Skip to content

Add new BitDataGrid component (#12502)#12504

Open
msynk wants to merge 18 commits into
bitfoundation:developfrom
msynk:12502-blazorui-new-datagrid
Open

Add new BitDataGrid component (#12502)#12504
msynk wants to merge 18 commits into
bitfoundation:developfrom
msynk:12502-blazorui-new-datagrid

Conversation

@msynk

@msynk msynk commented Jun 21, 2026

Copy link
Copy Markdown
Member

closes #12502

Summary by CodeRabbit

New Features

  • BitDataGrid overhaul: CSS-grid rendering with sticky header/footer, toolbar + column chooser, multi-sort priority, per-column filters, grouping with aggregate footers, frozen/detail rows, tree mode, virtualization, improved empty/loading/infinite states, keyboard cell navigation, inline editing (create/save/cancel/delete), row/column resize + reordering, and CSV export. Supports client, server (OnRead), and infinite (OnLoadMore) loading.
  • BitQuickGrid added: new <table>-based grid with sorting, pagination, optional virtualization, and resizable columns.

Breaking Changes

  • BitDataGrid API redesign (including generic type rename) with new selection and request/response models; legacy contracts removed.
  • Removed BitDirection.

Documentation / Demos

  • Updated DataGrid demo with new scenarios.
  • Added QuickGrid demo with expanded examples (including RTL/Persian).

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds a new BitDataGrid surface with expanded data modes, interaction features, and demo coverage, while moving the prior grid implementation to BitQuickGrid and updating its related contracts, styles, samples, and app wiring.

Changes

BitDataGrid buildout

Layer / File(s) Summary
Contracts and processing primitives
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/*, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/*, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
Adds the grid’s column model, descriptors, aggregates, accessors, comparers, and client-side filter/sort/group/aggregate helpers.
Data modes and interaction behaviors
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor, src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
Implements the grid’s refresh modes, rendering states, selection/editing/navigation actions, export, row/column operations, infinite scrolling, row and cell rendering, and updated layout styling.
Demo page and sample assets
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/*
Replaces the DataGrid demo with expanded examples, runtime handlers, declarative metadata, sample models/generators, and reduced demo styling.

BitQuickGrid extraction and migration

Layer / File(s) Summary
QuickGrid component and interop
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/*, src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/*
Renames the grid surface to BitQuickGrid and retargets the popup, resize, pagination, and virtualization wiring to the renamed component.
QuickGrid columns, sort, and provider contracts
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/*, src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/*, src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/*
Renames the column, sort, provider, and pagination contracts to the QuickGrid variants and updates their type references.
QuickGrid demos, shared DTO moves, and app wiring
src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/*, src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/*, src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs, src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json, src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
Adds the QuickGrid demo page and samples, moves demo DTO namespaces, updates navigation, and wires the demo stylesheet compiler mapping.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I hopped through columns, row by row,
New grids to greet and old ones glow.
Filters twinkle, sorts align,
QuickGrid wears a brighter sign.
A carrot toast to every span—
This bunny likes a well-wired plan.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes fit the grid split, but deleting the public BitDirection enum looks unrelated to the linked BitDataGrid/BitQuickGrid objectives. Restore BitDirection or document why it was intentionally removed as part of this issue; otherwise keep the PR scoped to the grid rename/addition.
Docstring Coverage ⚠️ Warning Docstring coverage is 36.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: adding the new BitDataGrid component.
Linked Issues check ✅ Passed The PR adds BitDataGrid, renames the old component to BitQuickGrid, and updates Extras/demo support to match the issue.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (18)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs-12-13 (1)

12-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Deterministic claim is broken by DateTime.Today

Line 29 makes generated data drift day-to-day, so the “reproducible” promise in Line 12 is not true across dates.

Suggested fix
 public static List<Product> Generate(int count, int seed = 42)
 {
     var rng = new Random(seed);
+    var baseDate = new DateTime(2024, 1, 1);
     var categories = Enum.GetValues<Category>();
     var list = new List<Product>(count);
@@
-                ReleaseDate = DateTime.Today.AddDays(-rng.Next(0, 2000)),
+                ReleaseDate = baseDate.AddDays(-rng.Next(0, 2000)),

Also applies to: 29-29

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs`
around lines 12 - 13, The Generate method in the SampleData class claims to be
deterministic and reproducible in its summary, but it uses DateTime.Today on
line 29 which changes daily and breaks the deterministic guarantee. Replace the
DateTime.Today usage with a fixed, seed-based date value that remains consistent
regardless of when the method is called. This ensures the data generation is
truly reproducible and matches the claim in the method's XML summary comment.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs-223-225 (1)

223-225: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Example 16 sample is missing the OnCellDoubleClick handler

Line 224 wires OnCellDoubleClick, but the paired C# snippet (Lines 230-231) does not define it.

Suggested fix
 void OnCellClick(BitDataGridCellEventArgs<Product> e) { /* e.Item, e.ColumnTitle, e.Value */ }
+void OnCellDoubleClick(BitDataGridCellEventArgs<Product> e) { /* ... */ }
 void OnCellContextMenu(BitDataGridCellEventArgs<Product> e) { /* e.Mouse.ClientX / e.Mouse.ClientY */ }

Also applies to: 230-231

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`
around lines 223 - 225, The Razor component in Example 16 declares an
OnCellDoubleClick event handler in the markup, but the corresponding C# method
is missing from the code-behind. Add the OnCellDoubleClick handler method in the
C# section of BitDataGridDemo.razor.samples.cs following the same pattern and
signature as the existing OnCellClick and OnCellContextMenu handler methods.
Ensure the method is defined in the appropriate location within the Example 16
sample code block so it matches the event binding in the markup.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs-148-163 (1)

148-163: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Honor all sort descriptors in the read handlers.

Both handlers only apply request.Sorts.FirstOrDefault(), so additional sort descriptors are ignored and multi-sort requests produce incorrect ordering.

Suggested fix
-        var sort = request.Sorts.FirstOrDefault();
-        if (sort is not null)
+        IOrderedEnumerable<Product>? ordered = null;
+        foreach (var sort in request.Sorts)
         {
             Func<Product, object> key = sort.ColumnId switch
             {
                 nameof(Product.Name) => p => p.Name,
                 nameof(Product.Category) => p => p.Category,
                 nameof(Product.Supplier) => p => p.Supplier,
                 nameof(Product.Price) => p => p.Price,
                 nameof(Product.Stock) => p => p.Stock,
+                nameof(Product.Rating) => p => p.Rating,
                 _ => p => p.Id
             };
-            query = sort.Direction == BitDataGridSortDirection.Descending
-                ? query.OrderByDescending(key)
-                : query.OrderBy(key);
+            ordered = ordered is null
+                ? (sort.Direction == BitDataGridSortDirection.Descending
+                    ? query.OrderByDescending(key)
+                    : query.OrderBy(key))
+                : (sort.Direction == BitDataGridSortDirection.Descending
+                    ? ordered.ThenByDescending(key)
+                    : ordered.ThenBy(key));
         }
+        if (ordered is not null) query = ordered;

Also applies to: 182-198

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`
around lines 148 - 163, The current implementation only processes the first sort
descriptor using request.Sorts.FirstOrDefault(), which causes multi-sort
requests to be ignored. Replace the if statement that checks for a single sort
with a loop that iterates through all items in request.Sorts collection. For
each sort descriptor, apply the appropriate sort operation (OrderBy or
OrderByDescending based on sort.Direction) to the query in sequence. The switch
statement logic for mapping sort.ColumnId to the appropriate property key should
remain the same, but needs to be executed for every sort descriptor in the
collection rather than just the first one.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs-203-205 (1)

203-205: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle empty final batches in infinite-scroll status text.

When no rows are returned, the current log renders an inverted range (start > end), which is misleading.

Suggested fix
-        var end = request.Skip + batch.Count;
-        infiniteLog = $"Batch #{infiniteRequests} → loaded rows {request.Skip + 1}–{end} ({batch.Count} rows)";
+        if (batch.Count == 0)
+        {
+            infiniteLog = $"Batch #{infiniteRequests} → no more rows to load.";
+        }
+        else
+        {
+            var end = request.Skip + batch.Count;
+            infiniteLog = $"Batch #{infiniteRequests} → loaded rows {request.Skip + 1}–{end} ({batch.Count} rows)";
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`
around lines 203 - 205, The infiniteLog message construction does not handle the
case when batch.Count is zero (empty final batch), resulting in a misleading
inverted range display (e.g., "loaded rows 101–100"). Add a conditional check
before assigning to infiniteLog to detect when batch.Count equals zero and
either skip the log update entirely or provide an alternative status message
that clearly indicates no additional rows were loaded, while preserving the
current log message logic for non-empty batches.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs-126-130 (1)

126-130: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear stale accessor when Field is removed.

Line 128 sets Accessor only when HasField is true; if Field changes from populated to null/empty, the previous accessor remains active.

Proposed fix
     protected override void OnParametersSet()
     {
         if (HasField)
             Accessor = BitDataGridPropertyAccessor<TItem>.For(Field!);
+        else
+            Accessor = null;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs`
around lines 126 - 130, In the OnParametersSet method of the BitDataGridColumn
class, the Accessor is only set when HasField is true, but when Field is cleared
or removed (HasField becomes false), the previous accessor remains active and is
never cleared. Add an else clause after the if (HasField) condition that sets
Accessor to null when HasField is false, ensuring stale accessors are properly
cleaned up when the Field parameter changes from a value to null or empty.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix Stylelint value-keyword-case violation in font stack.

Line 8 currently triggers lint errors; quote named font families so lint passes without changing fallback behavior.

Proposed fix
-    --bit-dtg-font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
+    --bit-dtg-font: 14px/1.4 system-ui, -apple-system, "Segoe UI", "Roboto", sans-serif;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss` at
line 8, The font stack in the --bit-dtg-font custom property on line 8 of
BitDataGrid.scss triggers a stylelint value-keyword-case violation because font
family names are not properly quoted. Quote all font family names in the font
stack (system-ui, -apple-system, Segoe UI, Roboto, and sans-serif) individually
in the declaration to ensure stylelint compliance while maintaining the same
fallback behavior.

Source: Linters/SAST tools

src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs-20-20 (1)

20-20: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CancellationToken is never populated by the grid.

The CancellationToken property exists but is never set when constructing BitDataGridReadRequest instances in LoadNextBatchAsync or LoadServerDataAsync. Consumers will always receive CancellationToken.None, making this property ineffective for request cancellation.

Consider wiring a CancellationTokenSource that cancels on component disposal or when a new request supersedes an in-flight one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs`
at line 20, The CancellationToken property in BitDataGridReadRequest is declared
but never initialized when instances are created in LoadNextBatchAsync and
LoadServerDataAsync methods. To fix this, create a CancellationTokenSource field
in the grid component and pass its Token property to the CancellationToken
property when constructing each BitDataGridReadRequest instance. Additionally,
dispose the previous CancellationTokenSource and create a new one for each
request to handle superseding in-flight requests, and ensure the
CancellationTokenSource is disposed when the component is disposed to properly
clean up resources.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs-557-566 (1)

557-566: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Dead code: || true makes !MultiSort ineffective.

Line 557's condition (!MultiSort || true) always evaluates to true, making the !MultiSort check dead code. Additionally, line 560 redundantly re-checks !additive.

This appears to be leftover from refactoring. Simplify to the intended logic:

🧹 Proposed fix
-        if (!additive && (!MultiSort || true))
-        {
-            // Single sort: clear others unless additive
-            if (!additive)
-            {
-                var keep = existing;
-                _sorts.Clear();
-                if (keep is not null) _sorts.Add(keep);
-            }
-        }
+        if (!additive)
+        {
+            // Single sort: clear others
+            var keep = existing;
+            _sorts.Clear();
+            if (keep is not null) _sorts.Add(keep);
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 557 - 566, The condition in the BitDataGrid.razor.cs sort handling
logic contains `|| true` on line 557, which makes the `!MultiSort` check
ineffective and represents dead code. Additionally, the inner if statement on
line 560 redundantly re-checks the `!additive` condition that was already
evaluated in the outer if. To fix this, remove the `|| true` portion from the
outer condition so it properly checks both `!additive && !MultiSort`, and remove
the redundant inner `if (!additive)` block, moving its contents (the sort
clearing logic) to directly execute within the outer if block since the
condition already guarantees additive is false.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs-1087-1101 (1)

1087-1101: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CSV export may throw if GetFormattedValue returns null.

GetFormattedValue(item) at line 1094 could potentially return null, and passing null to Escape() would cause a NullReferenceException on the .Contains() call.

🐛 Proposed fix
     public string ToCsv()
     {
         var cols = VisibleColumns.Where(c => c.HasField).ToList();
         var sb = new System.Text.StringBuilder();
         sb.AppendLine(string.Join(",", cols.Select(c => Escape(c.DisplayTitle))));
         var rows = IsServerMode ? _pageItems : _view;
         foreach (var item in rows)
-            sb.AppendLine(string.Join(",", cols.Select(c => Escape(c.GetFormattedValue(item)))));
+            sb.AppendLine(string.Join(",", cols.Select(c => Escape(c.GetFormattedValue(item) ?? ""))));
         return sb.ToString();

         static string Escape(string v)
-            => v.Contains(',') || v.Contains('"') || v.Contains('\n')
+            => v is null ? "" : v.Contains(',') || v.Contains('"') || v.Contains('\n')
                 ? "\"" + v.Replace("\"", "\"\"") + "\""
                 : v;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 1087 - 1101, The ToCsv() method passes the return value of
GetFormattedValue(item) directly to the Escape() helper function, but
GetFormattedValue can return null, causing a NullReferenceException when Escape
tries to call .Contains() on the null value. Fix this by adding null handling to
the Escape() method—either at the start of the method by converting null to an
empty string using the null coalescing operator, or by using the null coalescing
operator when calling GetFormattedValue(item) in the Select expression to ensure
Escape always receives a non-null string value.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs-74-84 (1)

74-84: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject empty/whitespace paths up front

For("")/For(" ") currently builds an accessor for the entire item object, which silently masks misconfigured columns and leads to incorrect sort/filter behavior.

Suggested patch
 public static BitDataGridPropertyAccessor<TItem> For(string path)
-    => Cache.GetOrAdd(path, Build);
+{
+    if (string.IsNullOrWhiteSpace(path))
+        throw new ArgumentException("Property path cannot be null, empty, or whitespace.", nameof(path));
+
+    return Cache.GetOrAdd(path, Build);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`
around lines 74 - 84, The For method does not validate the input path parameter
before caching and building the accessor. Add validation at the start of the For
method to check if the path is null, empty, or contains only whitespace
characters. If the path is invalid, throw an appropriate exception to reject the
invalid input and prevent the silent masking of misconfigured columns that
currently occurs when an empty or whitespace-only path is passed to For.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor-107-107 (1)

107-107: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix virtualized placeholder aria-rowindex offset.

Line 107 should account for the header row. Non-virtual rows start at index 2 (Line 54), but placeholders currently start at 1, which misreports row positions to assistive tech.

Suggested fix
-        <tr aria-rowindex="@(placeholderContext.Index + 1)">
+        <tr aria-rowindex="@(placeholderContext.Index + 2)">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor` at
line 107, The aria-rowindex attribute in the virtualized placeholder row
template is not accounting for the header row offset. The placeholder row on
line 107 uses placeholderContext.Index + 1, but it should use
placeholderContext.Index + 2 to match the offset used in non-virtual rows (which
start at index 2 to account for the header). Update the aria-rowindex expression
to add 2 instead of 1 to ensure correct row position reporting to assistive
technologies.
src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss-4-5 (1)

4-5: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove .scss extensions in new imports to satisfy Stylelint.

Lines 4-5 currently violate scss/load-partial-extension.

Suggested fix
-@import "../Components/QuickGrid/BitQuickGrid.scss";
-@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss";
+@import "../Components/QuickGrid/BitQuickGrid";
+@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss` around lines 4
- 5, Remove the `.scss` file extensions from the `@import` statements on lines
4-5. Change the imports for BitQuickGrid.scss and BitQuickGridPaginator.scss to
remove the `.scss` extension so they reference just the partial names without
the extension, which satisfies the scss/load-partial-extension Stylelint rule
that disallows explicit file extensions in SCSS imports.

Source: Linters/SAST tools

src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs-129-134 (1)

129-134: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Typo in XML documentation.

Line 131 has a typo: "A callback that supplies data for the rid" should be "A callback that supplies data for the grid".

📝 Proposed fix
     /// <summary>
-    /// A callback that supplies data for the rid.
+    /// A callback that supplies data for the grid.
     ///
     /// You should supply either <see cref="Items"/> or <see cref="ItemsProvider"/>, but not both.
     /// </summary>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 129 - 134, Fix the typo in the XML documentation comment for the
ItemsProvider parameter where it currently states "A callback that supplies data
for the rid" should be corrected to "A callback that supplies data for the
grid". Locate the summary element in the XML comment block preceding the
ItemsProvider property declaration and change the word "rid" to "grid" to
accurately describe what the callback supplies data for.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs-346-353 (1)

346-353: ⚠️ Potential issue | 🟡 Minor

Await SetTotalItemCountAsync calls to ensure exceptions are not swallowed.

Pagination?.SetTotalItemCountAsync(result.TotalItemCount) is not awaited. The method can perform asynchronous work through TotalItemCountChangedSubscribable.InvokeCallbacksAsync() or SetCurrentPageIndexAsync(), either of which may trigger component updates and can throw exceptions. Without awaiting, any exceptions from these async operations will be silently swallowed. The same pattern appears at line 392.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 346 - 353, The SetTotalItemCountAsync method call on the Pagination
object is not being awaited, which means any exceptions thrown by async
operations within it (such as
TotalItemCountChangedSubscribable.InvokeCallbacksAsync or
SetCurrentPageIndexAsync) will be silently swallowed. Add the await keyword
before the Pagination?.SetTotalItemCountAsync(result.TotalItemCount) call to
properly handle exceptions. Apply the same fix to both occurrences of this
pattern in the BitQuickGrid.razor.cs file.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss-15-16 (1)

15-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix transition declaration so the delay is applied.

At Line 15, transition-delay is overridden by the shorthand on Line 16, so the delay never takes effect.

💡 Suggested fix
-        transition-delay: 25ms;
-        transition: opacity linear 100ms;
+        transition: opacity linear 100ms 25ms;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss`
around lines 15 - 16, The shorthand transition property on line 16 overrides the
transition-delay specified on line 15, causing the delay to be lost. Combine
both properties into a single shorthand transition declaration that includes the
delay value as the fourth parameter: change the two separate lines to a single
transition property that specifies opacity, linear, the 100ms duration, and the
25ms delay in the correct order.

Source: Linters/SAST tools

src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs-31-35 (1)

31-35: ⚠️ Potential issue | 🟡 Minor

Update XML documentation generic type references.

The class BitQuickGridSort<TGridItem> has mismatched generic parameters in four methods' XML documentation. All instances use <see cref="BitQuickGridSort{T}"/> instead of <see cref="BitQuickGridSort{TGridItem}"/>, which will produce unresolved-cref warnings.

Apply the following changes to all four methods:

Required fixes
  • ByAscending method (lines 31, 35): Replace BitQuickGridSort{T} with BitQuickGridSort{TGridItem} in both the summary and returns documentation.
  • ByDescending method (lines 41, 45): Replace BitQuickGridSort{T} with BitQuickGridSort{TGridItem} in both the summary and returns documentation.
  • ThenAscending method (lines 51, 55): Replace BitQuickGridSort{T} with BitQuickGridSort{TGridItem} in both the summary and returns documentation.
  • ThenDescending method (lines 68, 72): Replace BitQuickGridSort{T} with BitQuickGridSort{TGridItem} in both the summary and returns documentation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs`
around lines 31 - 35, In the BitQuickGridSort class, update the XML
documentation for the four sorting methods (ByAscending, ByDescending,
ThenAscending, and ThenDescending) to use the correct generic type parameter.
Replace all instances of `BitQuickGridSort{T}` with
`BitQuickGridSort{TGridItem}` in the see cref tags within both the summary and
returns elements of each method's documentation to match the actual class
generic parameter name and resolve cref warnings.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor-20-20 (1)

20-20: ⚠️ Potential issue | 🟡 Minor

Add Rel attribute to protect the target="_blank" link.

BitLink does not automatically apply rel="noopener noreferrer" for external links. Add Rel="BitLinkRels.NoOpener | BitLinkRels.NoReferrer" to prevent opener access:

<BitLink Href="https://www.nuget.org/packages/Bit.BlazorUI.Extras" 
         Target="_blank"
         Rel="BitLinkRels.NoOpener | BitLinkRels.NoReferrer">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`
at line 20, The BitLink component with Target="_blank" is missing the Rel
attribute which is needed for security protection against opener access. Add the
Rel attribute to the BitLink component (around line 20) with the value
"BitLinkRels.NoOpener | BitLinkRels.NoReferrer" to properly secure the external
link. This will prevent the opened page from accessing the window.opener object.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor-101-101 (1)

101-101: ⚠️ Potential issue | 🟡 Minor

Use distinct @ref variables for the two product grids.

At lines 101 and 116, both BitQuickGrid instances share the same component reference @ref="productsDataGrid", causing the second grid to overwrite the first. The ODataSampleNameFilter property setter (in code-behind) refreshes only the last rendered grid instance, breaking the intended refresh behavior for the virtualized grid.

Suggested fix
-<BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Virtualize>
+<BitQuickGrid `@ref`="odataProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Virtualize>

-<BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">
+<BitQuickGrid `@ref`="loadingTemplateProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">

Also declare these fields in BitQuickGridDemo.razor.cs and update the ODataSampleNameFilter setter to refresh the correct grid reference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`
at line 101, The two BitQuickGrid instances in the template are sharing the same
`@ref` variable "productsDataGrid", causing the second grid to overwrite the first
reference. Create distinct `@ref` variables for each BitQuickGrid component (one
for the virtualized grid and one for the other grid), then declare corresponding
fields in the BitQuickGridDemo.razor.cs code-behind file for each of these new
references. Finally, update the ODataSampleNameFilter property setter to refresh
the correct grid instance based on which grid should be updated when the filter
changes.
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs (1)

447-462: 💤 Low value

Consider avoiding repeated List<string> allocations.

GetRowClass and GetRowStyle (lines 447-479) allocate a new List<string> on every row render. For grids with many rows, this could add GC pressure. Consider using a StringBuilder or direct string concatenation since you're only combining at most two values.

♻️ Possible simplification
     private string? GetRowClass(TGridItem item)
     {
-        var classes = new List<string>();
-
-        if (RowClass is not null)
-        {
-            classes.Add(RowClass);
-        }
-
-        if (RowClassSelector is not null)
-        {
-            classes.Add(RowClassSelector(item));
-        }
-
-        return classes.Any() ? string.Join(' ', classes) : null;
+        var baseClass = RowClass;
+        var selectorClass = RowClassSelector?.Invoke(item);
+        
+        return (baseClass, selectorClass) switch
+        {
+            (not null, not null) => $"{baseClass} {selectorClass}",
+            (not null, null) => baseClass,
+            (null, not null) => selectorClass,
+            _ => null
+        };
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 447 - 462, The GetRowClass and GetRowStyle methods allocate a new
List<string> on every row render, which creates unnecessary garbage collection
pressure when rendering large grids. Replace the List<string> allocation and
join pattern with direct string concatenation in both methods. Since you're
combining at most two values in each method (RowClass and RowClassSelector for
GetRowClass, and similarly for GetRowStyle), you can build the result string
directly by checking the conditions and concatenating the values with a space
separator only when both values exist, avoiding the List allocation entirely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 448-475: In the LoadNextBatchAsync method, the OnLoadMore callback
invocation can throw an exception that causes the _infiniteLoading flag to
remain true, permanently blocking further loads. Wrap the entire logic starting
from the OnLoadMore call through the state updates in a try-finally block,
ensuring that _infiniteLoading is set to false and StateHasChanged is called in
the finally clause. This guarantees that _infiniteLoading will be reset even if
OnLoadMore throws an exception, allowing users to recover and retry loading more
data.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 144-153: The Sum and Average aggregation calculations in the
BitDataGridDataProcessor class use double type for accumulating values, which
loses precision for decimal/currency columns. Replace the double sum variable
and the TryToDouble method calls with decimal-based logic to preserve numeric
precision. Ensure the accumulation and calculation logic in the Sum case (around
lines 147-152) uses decimal arithmetic throughout, and apply the same fix to the
related aggregation logic mentioned in lines 174-180 to maintain consistency
across all numeric aggregate operations.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 95-97: The compiled getter in BitDataGridPropertyAccessor for
nested property paths like Address.City is not null-safe. When an intermediate
property (like Address) is null, the expression throws an exception instead of
returning null. Modify the expression tree construction before creating the
Expression.Lambda to include null checks at each level of the property chain.
This should use Expression.Condition to test if each intermediate property
access is null and return null early if any level is null, allowing sorting and
filtering to handle partial object graphs gracefully without runtime failures.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs`:
- Around line 7-10: The BitDataGridReadResult constructor accepts `items` and
`totalCount` parameters without validation, which allows null items and negative
totalCount values to pass through and corrupt the grid state. Add argument guard
clauses at the start of the BitDataGridReadResult constructor to validate that
items is not null and totalCount is greater than or equal to zero, throwing
appropriate ArgumentException or ArgumentNullException if validation fails,
ensuring invalid OnRead outputs fail immediately rather than causing downstream
paging issues.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Line 47: The call to colOptions.scrollIntoViewIfNeeded() is using a
non-standard browser API that is not supported in Firefox and will cause runtime
errors. Replace this single method call with a check that first tests if the
method exists before calling it, and falls back to the standard scrollIntoView()
API if scrollIntoViewIfNeeded is not available. This ensures the column-options
interaction works across all browsers including Firefox.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor`:
- Around line 108-110: The icon-only buttons with class "bit-qkg-cob" that
trigger Grid.ShowColumnOptions(this) at lines 110 and 129 lack accessible names,
making them unannounced to assistive technologies. Add an aria-label attribute
to both button elements that provides a descriptive name for the column options
button (such as "Column options" or similar descriptive text). This ensures
screen readers will properly announce the button's purpose to users with
disabilities.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 657-660: In the code where the OData filter string is constructed
using the query.Add method, the user input from _odataSampleNameFilter is being
directly interpolated without escaping, which creates an OData injection
vulnerability. To fix this, before interpolating _odataSampleNameFilter into the
filter string literal, escape any single quotes in the user input by replacing
each single quote character with two consecutive single quotes, as required by
the OData specification. This ensures that user input containing single quotes
like test' or '1'='1 will be treated as literal string data rather than being
able to break out of or modify the filter expression.
- Line 669: In the productsItemsProvider method at the GetFromJsonAsync call on
line 669, add the cancellation token parameter req.CancellationToken as the
third argument to match the pattern already established in the
foodRecallItemsProvider method. This will ensure the HTTP request can properly
respond to cancellation requests from the grid.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss`:
- Line 12: The stylelint rule `selector-pseudo-element-no-unknown` flags
`::deep` pseudo-elements at lines 12, 62, 89, and 122 in the
BitQuickGridDemo.razor.scss file because `::deep` is a Blazor-specific
pseudo-element not part of standard CSS. Fix this by adding
`"selector-pseudo-element-no-unknown": null` to the rules section in
`.stylelintrc.json` to globally disable this rule (recommended approach), or
alternatively wrap each `::deep` block with stylelint disable and re-enable
comments (/* stylelint-disable-next-line selector-pseudo-element-no-unknown */
before and /* stylelint-enable */ after) if a file-local approach is preferred.

---

Minor comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 557-566: The condition in the BitDataGrid.razor.cs sort handling
logic contains `|| true` on line 557, which makes the `!MultiSort` check
ineffective and represents dead code. Additionally, the inner if statement on
line 560 redundantly re-checks the `!additive` condition that was already
evaluated in the outer if. To fix this, remove the `|| true` portion from the
outer condition so it properly checks both `!additive && !MultiSort`, and remove
the redundant inner `if (!additive)` block, moving its contents (the sort
clearing logic) to directly execute within the outer if block since the
condition already guarantees additive is false.
- Around line 1087-1101: The ToCsv() method passes the return value of
GetFormattedValue(item) directly to the Escape() helper function, but
GetFormattedValue can return null, causing a NullReferenceException when Escape
tries to call .Contains() on the null value. Fix this by adding null handling to
the Escape() method—either at the start of the method by converting null to an
empty string using the null coalescing operator, or by using the null coalescing
operator when calling GetFormattedValue(item) in the Select expression to ensure
Escape always receives a non-null string value.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss`:
- Line 8: The font stack in the --bit-dtg-font custom property on line 8 of
BitDataGrid.scss triggers a stylelint value-keyword-case violation because font
family names are not properly quoted. Quote all font family names in the font
stack (system-ui, -apple-system, Segoe UI, Roboto, and sans-serif) individually
in the declaration to ensure stylelint compliance while maintaining the same
fallback behavior.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs`:
- Around line 126-130: In the OnParametersSet method of the BitDataGridColumn
class, the Accessor is only set when HasField is true, but when Field is cleared
or removed (HasField becomes false), the previous accessor remains active and is
never cleared. Add an else clause after the if (HasField) condition that sets
Accessor to null when HasField is false, ensuring stale accessors are properly
cleaned up when the Field parameter changes from a value to null or empty.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 74-84: The For method does not validate the input path parameter
before caching and building the accessor. Add validation at the start of the For
method to check if the path is null, empty, or contains only whitespace
characters. If the path is invalid, throw an appropriate exception to reject the
invalid input and prevent the silent masking of misconfigured columns that
currently occurs when an empty or whitespace-only path is passed to For.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs`:
- Line 20: The CancellationToken property in BitDataGridReadRequest is declared
but never initialized when instances are created in LoadNextBatchAsync and
LoadServerDataAsync methods. To fix this, create a CancellationTokenSource field
in the grid component and pass its Token property to the CancellationToken
property when constructing each BitDataGridReadRequest instance. Additionally,
dispose the previous CancellationTokenSource and create a new one for each
request to handle superseding in-flight requests, and ensure the
CancellationTokenSource is disposed when the component is disposed to properly
clean up resources.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor`:
- Line 107: The aria-rowindex attribute in the virtualized placeholder row
template is not accounting for the header row offset. The placeholder row on
line 107 uses placeholderContext.Index + 1, but it should use
placeholderContext.Index + 2 to match the offset used in non-virtual rows (which
start at index 2 to account for the header). Update the aria-rowindex expression
to add 2 instead of 1 to ensure correct row position reporting to assistive
technologies.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 129-134: Fix the typo in the XML documentation comment for the
ItemsProvider parameter where it currently states "A callback that supplies data
for the rid" should be corrected to "A callback that supplies data for the
grid". Locate the summary element in the XML comment block preceding the
ItemsProvider property declaration and change the word "rid" to "grid" to
accurately describe what the callback supplies data for.
- Around line 346-353: The SetTotalItemCountAsync method call on the Pagination
object is not being awaited, which means any exceptions thrown by async
operations within it (such as
TotalItemCountChangedSubscribable.InvokeCallbacksAsync or
SetCurrentPageIndexAsync) will be silently swallowed. Add the await keyword
before the Pagination?.SetTotalItemCountAsync(result.TotalItemCount) call to
properly handle exceptions. Apply the same fix to both occurrences of this
pattern in the BitQuickGrid.razor.cs file.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss`:
- Around line 15-16: The shorthand transition property on line 16 overrides the
transition-delay specified on line 15, causing the delay to be lost. Combine
both properties into a single shorthand transition declaration that includes the
delay value as the fourth parameter: change the two separate lines to a single
transition property that specifies opacity, linear, the 100ms duration, and the
25ms delay in the correct order.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs`:
- Around line 31-35: In the BitQuickGridSort class, update the XML documentation
for the four sorting methods (ByAscending, ByDescending, ThenAscending, and
ThenDescending) to use the correct generic type parameter. Replace all instances
of `BitQuickGridSort{T}` with `BitQuickGridSort{TGridItem}` in the see cref tags
within both the summary and returns elements of each method's documentation to
match the actual class generic parameter name and resolve cref warnings.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 4-5: Remove the `.scss` file extensions from the `@import`
statements on lines 4-5. Change the imports for BitQuickGrid.scss and
BitQuickGridPaginator.scss to remove the `.scss` extension so they reference
just the partial names without the extension, which satisfies the
scss/load-partial-extension Stylelint rule that disallows explicit file
extensions in SCSS imports.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 148-163: The current implementation only processes the first sort
descriptor using request.Sorts.FirstOrDefault(), which causes multi-sort
requests to be ignored. Replace the if statement that checks for a single sort
with a loop that iterates through all items in request.Sorts collection. For
each sort descriptor, apply the appropriate sort operation (OrderBy or
OrderByDescending based on sort.Direction) to the query in sequence. The switch
statement logic for mapping sort.ColumnId to the appropriate property key should
remain the same, but needs to be executed for every sort descriptor in the
collection rather than just the first one.
- Around line 203-205: The infiniteLog message construction does not handle the
case when batch.Count is zero (empty final batch), resulting in a misleading
inverted range display (e.g., "loaded rows 101–100"). Add a conditional check
before assigning to infiniteLog to detect when batch.Count equals zero and
either skip the log update entirely or provide an alternative status message
that clearly indicates no additional rows were loaded, while preserving the
current log message logic for non-empty batches.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 223-225: The Razor component in Example 16 declares an
OnCellDoubleClick event handler in the markup, but the corresponding C# method
is missing from the code-behind. Add the OnCellDoubleClick handler method in the
C# section of BitDataGridDemo.razor.samples.cs following the same pattern and
signature as the existing OnCellClick and OnCellContextMenu handler methods.
Ensure the method is defined in the appropriate location within the Example 16
sample code block so it matches the event binding in the markup.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs`:
- Around line 12-13: The Generate method in the SampleData class claims to be
deterministic and reproducible in its summary, but it uses DateTime.Today on
line 29 which changes daily and breaks the deterministic guarantee. Replace the
DateTime.Today usage with a fixed, seed-based date value that remains consistent
regardless of when the method is called. This ensures the data generation is
truly reproducible and matches the claim in the method's XML summary comment.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 20: The BitLink component with Target="_blank" is missing the Rel
attribute which is needed for security protection against opener access. Add the
Rel attribute to the BitLink component (around line 20) with the value
"BitLinkRels.NoOpener | BitLinkRels.NoReferrer" to properly secure the external
link. This will prevent the opened page from accessing the window.opener object.
- Line 101: The two BitQuickGrid instances in the template are sharing the same
`@ref` variable "productsDataGrid", causing the second grid to overwrite the first
reference. Create distinct `@ref` variables for each BitQuickGrid component (one
for the virtualized grid and one for the other grid), then declare corresponding
fields in the BitQuickGridDemo.razor.cs code-behind file for each of these new
references. Finally, update the ODataSampleNameFilter property setter to refresh
the correct grid instance based on which grid should be updated when the filter
changes.

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 447-462: The GetRowClass and GetRowStyle methods allocate a new
List<string> on every row render, which creates unnecessary garbage collection
pressure when rendering large grids. Replace the List<string> allocation and
join pattern with direct string concatenation in both methods. Since you're
combining at most two values in each method (RowClass and RowClassSelector for
GetRowClass, and similarly for GetRowStyle), you can build the result string
directly by checking the conditions and concatenating the values with a space
separator only when both values exist, avoiding the List allocation entirely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7038154f-12e2-424f-a8d9-7750b95219c1

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and fdaa067.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (5)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts Outdated
@msynk

msynk commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@msynk

msynk commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (5)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss (1)

218-227: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Respect reduced-motion user preference for spinner/shimmer animations.

The grid always animates loading indicators. Add a prefers-reduced-motion fallback so motion-sensitive users are not forced into continuous animation.

Suggested fix
 `@keyframes` bit-dtg-spin { to { transform: rotate(360deg); } }
@@
 `@keyframes` bit-dtg-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
+
+@media (prefers-reduced-motion: reduce) {
+    .bit-dtg-spinner,
+    .bit-dtg-skeleton {
+        animation: none;
+    }
+}

Also applies to: 231-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss` around
lines 218 - 227, The BitDataGrid spinner and shimmer animations do not respect
user accessibility preferences for reduced motion. Wrap the animation properties
in the `.bit-dtg-spinner` rule and the related animation rules (lines 231-243)
in a `@media (prefers-reduced-motion: no-preference)` query so animations only
play for users who have not requested reduced motion. For users with
`prefers-reduced-motion: reduce`, provide fallback styles that disable the
animation property, ensuring the spinner/shimmer elements remain visible but
static.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor (2)

265-270: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add @key to virtualized row for identity consistency.

The infinite (line 246) and paged (line 275) render paths use @key="item", but the virtualized path does not. Adding @key ensures consistent item identity tracking across all rendering modes, which helps Blazor optimize DOM updates when items are reordered or modified.

♻️ Suggested fix
 else if (UseVirtualization)
 {
     <Virtualize Items="VirtualRows" ItemSize="RowHeight" Context="item" TItem="TItem">
-        <BitDataGridRow TItem="TItem" Grid="this" Item="item" />
+        <BitDataGridRow TItem="TItem" Grid="this" Item="item" `@key`="item" />
     </Virtualize>
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`
around lines 265 - 270, The virtualized rendering path in the UseVirtualization
condition is missing the `@key` directive on the BitDataGridRow component, while
the infinite and paged rendering paths both include `@key`="item". Add `@key`="item"
to the BitDataGridRow component inside the Virtualize block to ensure consistent
item identity tracking across all rendering modes and allow Blazor to properly
optimize DOM updates when items are reordered or modified.

336-338: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Cache VirtualRows to avoid repeated list allocation.

VirtualRows calls ToList() on every access when _view is not an ICollection<TItem>. Since Virtualize may access Items multiple times during rendering, this creates unnecessary allocations. Consider caching the list when the view changes instead of computing it on each access.

♻️ Suggested approach

Add a cached field that is populated when _view changes (e.g., in ProcessClientData or RefreshAsync):

// In code-behind
private ICollection<TItem>? _cachedVirtualRows;

// Update when _view changes
_cachedVirtualRows = _view as ICollection<TItem> ?? _view.ToList();

Then in the Razor:

-private ICollection<TItem> VirtualRows => _view as ICollection<TItem> ?? _view.ToList();
+private ICollection<TItem> VirtualRows => _cachedVirtualRows ?? [];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`
around lines 336 - 338, The VirtualRows property currently calls ToList() on
every access when _view is not an ICollection<TItem>, causing repeated
allocations during rendering cycles. Add a private cached field to store the
computed virtual rows collection, then populate this cache whenever _view
changes (such as in ProcessClientData or RefreshAsync methods). Update the
VirtualRows property to return the cached field instead of computing the
collection on each access, ensuring the cache remains synchronized whenever the
underlying _view is modified.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (1)

220-222: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider caching VisibleColumns to avoid repeated allocations.

VisibleColumns creates a new list on every property access via .ToList(). This property is accessed frequently during rendering (in BitDataGridRow, keyboard navigation, column spanning, etc.). Consider caching the result and invalidating when columns change.

♻️ Suggested caching pattern
+ private IReadOnlyList<BitDataGridColumn<TItem>>? _visibleColumnsCache;
+ private int _columnsVersion;

- internal IReadOnlyList<BitDataGridColumn<TItem>> VisibleColumns => _columns.Where(c => c.Visible).ToList();
+ internal IReadOnlyList<BitDataGridColumn<TItem>> VisibleColumns => _visibleColumnsCache ??= _columns.Where(c => c.Visible).ToList();

  internal void AddColumn(BitDataGridColumn<TItem> column)
  {
      if (_columns.Contains(column)) return;
      _columns.Add(column);
      _columnsById[column.Id] = column;
+     _visibleColumnsCache = null;
      InvokeAsync(StateHasChanged);
  }

  internal void RemoveColumn(BitDataGridColumn<TItem> column)
  {
      if (_columns.Remove(column))
      {
          _columnsById.Remove(column.Id);
+         _visibleColumnsCache = null;
          InvokeAsync(StateHasChanged);
      }
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 220 - 222, The VisibleColumns property in the BitDataGrid class
creates a new list on every access via .ToList(), causing unnecessary
allocations since this property is accessed frequently during rendering.
Implement caching by creating a private backing field to store the visible
columns list, update this cached list whenever the underlying _columns
collection changes (add invalidation logic in methods that modify _columns such
as column add/remove operations), and modify the VisibleColumns property to
return the cached list instead of recomputing it with .Where().ToList() on each
access. Ensure the cache is refreshed whenever columns are added, removed, or
their visibility changes.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs (1)

1-26: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Event args exposes mutable Column reference; document stability assumption.

The Column property holds a mutable reference to BitDataGridColumn<TItem>. While event args should ideally be immutable snapshots, this design is acceptable in the Blazor component context—columns are stable objects throughout the grid's lifetime. However, for future maintainers, consider adding an inline comment documenting this assumption so any refactoring (e.g., if columns become dynamically mutated) surfaces the design risk.

Alternatively, if column mutability becomes a concern, capture immutable column metadata (Id, DisplayTitle) directly into the event args instead of the Column reference itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs`
around lines 1 - 26, The `Column` property in the
`BitDataGridCellEventArgs<TItem>` class holds a mutable reference to a
`BitDataGridColumn<TItem>` object, which violates the principle that event args
should be immutable snapshots. Add an inline XML comment above the `Column`
property that documents the design assumption that column objects remain stable
throughout the grid's lifetime and warns future maintainers about the
refactoring risk if columns become dynamically mutated. Alternatively, if
mutable column objects become a concern, refactor the event args to capture only
the immutable column metadata directly (such as `Id` and `DisplayTitle`) instead
of holding a reference to the entire mutable Column object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts`:
- Around line 10-33: The dispose method only removes the scroll event listener,
but the setTimeout on line 28 and requestAnimationFrame on line 22 can schedule
the check function to execute after disposal, causing the
dotNetRef.invokeMethodAsync call on line 15 to run on a disposed component and
trigger unhandled rejections. Add a disposed flag that is set to true in the
dispose function, then guard the check function to return early if disposed
before calling dotNetRef.invokeMethodAsync, ensuring no interop calls occur
after the component has been cleaned up.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 87-90: The Path property in BitDataGridGroup is being constructed
using keyText, which is a formatted display value from
column.FormatValue(g.Key). This can cause path collisions when different keys
produce identical display values, leading to incorrect collapse/expand state
reuse. Change the path construction on line 89 to use the actual key value g.Key
instead of the formatted keyText when building the path identifier, while
keeping keyText available for display purposes elsewhere.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 115-124: The setter construction in the nested property handling
(around the Expression.Assign call in the setter creation block) does not
include null checks for intermediate properties in the chain, unlike the getter
which safely handles nulls. Add null-guard checks in the expression tree to
verify that intermediate properties are not null before attempting to assign to
nested properties. This should prevent SetValue from throwing exceptions when
intermediate nodes in the property path are null.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs`:
- Around line 8-9: The Priority property in BitDataGridSortDescriptor class has
no default value, causing it to default to 0 in C#. Since the documentation
states "1 = primary" and the sorting logic uses ascending Priority values, a
default of 0 makes unset descriptors become higher priority than those
explicitly set to 1, contradicting the documented behavior. Assign a default
value to the Priority property (consider using int.MaxValue or another
appropriate high value) so that explicitly set priorities take precedence over
uninitialized descriptors and align with the documented sort precedence where 1
is primary.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 373-377: The count calculation on line 376 within the Pagination
null check in the BitQuickGrid class can produce negative values when
request.StartIndex exceeds the remaining items in the page window, which then
causes invalid negative arguments to be passed to Take() method calls downstream
around lines 416-419. Modify the count assignment to clamp the result to a
minimum of zero by wrapping the subtraction expression with Math.Max(0, ...) to
ensure count is never negative, preventing the negative Take argument issue.
- Around line 327-337: The `_pendingDataLoadCancellationTokenSource`
CancellationTokenSource instances are not being properly disposed, causing
resource leaks on frequent refreshes. When canceling the previous token source
at the start of the method and when setting the field to null (in both the
Virtualize branch and the other branch referenced at line 346-352), you must
explicitly dispose each replaced instance before assigning a new one or clearing
the field. Ensure that any old CancellationTokenSource is disposed immediately
after Cancel is called, and dispose the current instance before setting it to
null.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss`:
- Around line 68-69: The border-inline-start property on line 69 uses a
hardcoded black color instead of the theme token variable used on line 68 for
border-color. Replace the hardcoded black value in the border-inline-start
property with the same theme variable (--bit-clr-brd-pri) to ensure the
resize-handle border color is consistent with the theme and follows the same
design token pattern.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Around line 41-45: The transform style on colOptions is only set when overhang
exists in the if block, but when the popup is opened again without overhang, the
previous transform value persists and causes misalignment. Add an else clause
after the overhang check to explicitly clear the transform by setting
colOptions.style.transform to an empty string or "none" when neither
leftOverhang nor rightOverhang is true.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs`:
- Line 36: The InvalidOperationException thrown in the
AsyncQueryExecutorSupplier class references a non-existent package
Microsoft.AspNetCore.Components.BitQuickGrid.EntityFrameworkAdapter and method
AddBitQuickGridEntityFrameworkAdapter that do not exist in the codebase. Update
the exception message to instead reference the actual correct approach using the
IAsyncQueryExecutor abstraction that is mentioned in the existing comments,
providing clear guidance on how developers should implement a custom query
executor to handle Entity Framework queries properly. Ensure the updated message
includes actionable steps or references to documentation so developers can
resolve the issue.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 4-5: Remove the .scss file extensions from the two import
statements in the extra-components.scss file. The imports for BitQuickGrid and
BitQuickGridPaginator should reference the files without the .scss extension
(e.g., change `@import` "../Components/QuickGrid/BitQuickGrid.scss" to `@import`
"../Components/QuickGrid/BitQuickGrid"). This will comply with the
scss/load-partial-extension stylelint rule.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 331-344: The LoadData sample in the filtering and sorting sections
always applies operations to the Name property regardless of which column was
actually requested. Modify the filtering loop to check the field/column name
from the filter request (likely available as a property on the filter object)
and conditionally apply the Contains filter to the appropriate property (Name,
Id, or Price). Similarly, update the sorting section to check which column is
being sorted from the sort request object and apply OrderBy or OrderByDescending
to the correct property instead of always using Name.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Around line 164-166: The BitButton component used for the row expand/collapse
toggle lacks an accessible label for assistive technologies. Add an
accessibility attribute (such as AriaLabel or Title) to the BitButton element
that contains the ChevronDown/ChevronRight icon. The label should clearly
describe the button's purpose, such as indicating whether it will expand or
collapse the row details, and should update dynamically based on the same
condition used to toggle the IconName property
(expandedRowTemplateCodes.Contains(context.Code)).

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 68-69: The Description strings in the BitQuickGridDemo class
contain user-facing typos at multiple locations. On line 68, the word "rid"
should be corrected to "grid" in the description text. Additionally, fix any
other grammar or wording errors in the Description properties at lines 160 and
254. Review each Description string for clarity and ensure they are properly
formatted for documentation display purposes.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs`:
- Around line 726-729: The OData filter query being constructed in the query.Add
call with the contains function is vulnerable to syntax errors when
_odataSampleNameFilter contains single quotes. Escape the user input value by
applying Replace("'", "''") to _odataSampleNameFilter before interpolating it
into the OData filter string. This will properly escape single quotes according
to OData standards and prevent filter syntax breakage.
- Line 738: The GetFromJsonAsync method calls in the sample code are missing the
cancellation token parameter. Add req.CancellationToken as a parameter to both
GetFromJsonAsync calls at lines 738 and 906 to ensure proper cancellation
propagation, making the sample code consistent with the runtime demo provider
implementation.

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 265-270: The virtualized rendering path in the UseVirtualization
condition is missing the `@key` directive on the BitDataGridRow component, while
the infinite and paged rendering paths both include `@key`="item". Add `@key`="item"
to the BitDataGridRow component inside the Virtualize block to ensure consistent
item identity tracking across all rendering modes and allow Blazor to properly
optimize DOM updates when items are reordered or modified.
- Around line 336-338: The VirtualRows property currently calls ToList() on
every access when _view is not an ICollection<TItem>, causing repeated
allocations during rendering cycles. Add a private cached field to store the
computed virtual rows collection, then populate this cache whenever _view
changes (such as in ProcessClientData or RefreshAsync methods). Update the
VirtualRows property to return the cached field instead of computing the
collection on each access, ensuring the cache remains synchronized whenever the
underlying _view is modified.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 220-222: The VisibleColumns property in the BitDataGrid class
creates a new list on every access via .ToList(), causing unnecessary
allocations since this property is accessed frequently during rendering.
Implement caching by creating a private backing field to store the visible
columns list, update this cached list whenever the underlying _columns
collection changes (add invalidation logic in methods that modify _columns such
as column add/remove operations), and modify the VisibleColumns property to
return the cached list instead of recomputing it with .Where().ToList() on each
access. Ensure the cache is refreshed whenever columns are added, removed, or
their visibility changes.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss`:
- Around line 218-227: The BitDataGrid spinner and shimmer animations do not
respect user accessibility preferences for reduced motion. Wrap the animation
properties in the `.bit-dtg-spinner` rule and the related animation rules (lines
231-243) in a `@media (prefers-reduced-motion: no-preference)` query so
animations only play for users who have not requested reduced motion. For users
with `prefers-reduced-motion: reduce`, provide fallback styles that disable the
animation property, ensuring the spinner/shimmer elements remain visible but
static.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs`:
- Around line 1-26: The `Column` property in the
`BitDataGridCellEventArgs<TItem>` class holds a mutable reference to a
`BitDataGridColumn<TItem>` object, which violates the principle that event args
should be immutable snapshots. Add an inline XML comment above the `Column`
property that documents the design assumption that column objects remain stable
throughout the grid's lifetime and warns future maintainers about the
refactoring risk if columns become dynamically mutated. Alternatively, if
mutable column objects become a concern, refactor the event args to capture only
the immutable column metadata directly (such as `Id` and `DisplayTitle`) instead
of holding a reference to the entire mutable Column object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 908cae7a-5590-47b0-bff5-7de18c8d32e9

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 8acd2aa.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs Outdated
@msynk

msynk commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs (1)

89-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a typed, culture-invariant identity for group path keys.

Line 91 still relies on string interpolation of g.Key, which can collide when distinct keys produce identical text and causes incorrect collapse/expand state reuse.

Suggested fix
                 var keyText = column.FormatValue(g.Key);
                 var items = g.ToList();
                 // Use the raw key (not the formatted display text) for the path identifier so that
                 // distinct keys producing identical display values don't collide and share collapse/expand state.
-                var path = $"{parentPath}/{level}:{g.Key}";
+                var keyIdentity = g.Key is null
+                    ? "null"
+                    : g.Key is IFormattable f
+                        ? $"{g.Key.GetType().FullName}:{f.ToString(null, CultureInfo.InvariantCulture)}"
+                        : $"{g.Key.GetType().FullName}:{g.Key}";
+                var path = $"{parentPath}/{level}:{descriptor.ColumnId}:{keyIdentity}";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`
around lines 89 - 92, In the BitDataGridDataProcessor.cs file where the path
variable is constructed using string interpolation with g.Key, replace the
simple string concatenation of g.Key with a typed, culture-invariant identifier
that prevents collisions between distinct keys producing identical text. Instead
of relying on the string representation of g.Key which can be culture-dependent
and cause identical display values to collide, use a robust uniqueness mechanism
such as the key's hash code or another unique type-safe identifier that
preserves the actual key identity across different key instances.
🧹 Nitpick comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs (1)

9-9: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider making FormattedValue required or nullable for consistency.

The FormattedValue property has a default value of string.Empty but is not marked as required, while ColumnId is required. This creates an inconsistent initialization pattern. If FormattedValue is always expected to have a value, mark it required; if it's truly optional, consider making it string? with a null default instead.

♻️ Proposed options

Option 1: Make it required

-    public string FormattedValue { get; init; } = string.Empty;
+    public required string FormattedValue { get; init; }

Option 2: Make it nullable

-    public string FormattedValue { get; init; } = string.Empty;
+    public string? FormattedValue { get; init; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs`
at line 9, The `FormattedValue` property in the `BitDataGridAggregateResult`
class has a default value of `string.Empty` but is not marked as `required`,
creating an inconsistency with the `ColumnId` property which is required. To fix
this, either mark `FormattedValue` as `required` to match the pattern of
`ColumnId`, or change its type to `string?` and set the default value to `null`
to properly represent it as an optional nullable property. Choose the approach
that best represents the semantic intent of this property in your data model.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs (1)

25-25: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Inconsistent use of required modifier.

The Value and Mouse properties are not marked as required, but based on the construction code in BitDataGrid.razor.cs:947, they are always explicitly set:

new() { Item = item, Column = column, Value = column.GetValue(item), Mouse = e }

This is inconsistent with Item and Column being marked required. Either mark Value and Mouse as required for consistency and compile-time safety, or document why they are intentionally optional.

♻️ Proposed fix for consistency
     /// <summary>The raw value of the cell.</summary>
-    public object? Value { get; init; }
+    public required object? Value { get; init; }

     /// <summary>The underlying browser mouse event.</summary>
-    public MouseEventArgs Mouse { get; init; } = new();
+    public required MouseEventArgs Mouse { get; init; }

Also applies to: 28-28

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs`
at line 25, The BitDataGridCellEventArgs class has inconsistent use of the
required modifier. The Value property at line 25 and Mouse property at line 28
are not marked as required, while Item and Column properties are. Since the
construction code in BitDataGrid.razor.cs always explicitly sets all four
properties when creating new instances, either add the required modifier to the
Value and Mouse properties to match Item and Column for consistency and
compile-time safety, or add documentation comments explaining why these two
properties are intentionally optional despite always being set during
construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 161-162: The _selected HashSet stores TItem references, but when
items are refreshed from server or through infinite scrolling, new object
instances are created with the same key value. Since reference equality fails
for these new instances, previously selected rows appear unselected and
select-all state becomes incorrect. Change the selection mechanism to track
items by their KeyField value instead of object reference. This means either
storing the key values in the _selected collection instead of TItem instances,
or implementing a custom equality comparer for TItem that compares based on the
KeyField value. Apply the same fix to _expandedDetails which has the same issue
at lines 227-230 and 697-715.
- Around line 458-478: The LoadNextBatchAsync method has a race condition where
older OnLoadMore responses can still mutate state after newer requests are
initiated, causing stale data. Implement a version-guard pattern by tracking the
current request version before calling OnLoadMore (similar to how
ResetLoadCancellation is used), then verify this version matches the current
state before applying the response mutations to _infiniteItems, _view,
_pageItems, and _footerAggregates. Apply the same version-guard pattern to both
the LoadNextBatchAsync method shown in the diff and the other similar location
mentioned in the comment (lines 543-557).
- Around line 1137-1139: The ReorderStickyStyle, DetailStickyStyle, and
SelectStickyStyle properties hardcode "left:" for positioning but should use
"right:" when the page is in RTL (right-to-left) mode. Modify each of these
three property definitions to conditionally output either "left:" or "right:"
based on the current RTL state, similar to how the FrozenStyle property handles
directional positioning. Check the BitDataGrid component for any existing RTL
detection logic or boolean flag you can use to determine when to use "right:"
instead of "left:".
- Around line 581-587: The condition on line 581 in the sort-clearing logic uses
`if (!additive && !MultiSort)` which requires both conditions to be true, but
this causes existing sorts to persist when MultiSort=true and a non-additive
click occurs (without Ctrl/Meta). The fix is to change the condition to only
check `if (!additive)` so that prior sorts are cleared whenever it is a
non-additive action, regardless of the MultiSort setting, making the behavior
consistent with Line 393 where additive mode is tied to Ctrl/Meta detection.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`:
- Around line 38-45: The column spanning logic in BitDataGridRow.razor skips
rendering columns when span is greater than 1, but the keyboard focus and
navigation model still operates using the full VisibleColumns index space,
creating a mismatch where focus targets can point to non-existent cells. Update
the focus/navigation model to map column indices to account for spanned columns,
ensuring that when columns are skipped due to spanning in the rendering loop
(where skip is decremented and columns are skipped), the corresponding focus
targets and tabindex assignments properly exclude those skipped column indices
so that keyboard focus always lands on a valid rendered BitDataGridCell with
tabindex=0.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 374-381: The Task.Delay(100) call in the debounce logic does not
respect the cancellation token, causing unnecessary delays even when virtualized
requests have already been canceled. Pass the request.CancellationToken as a
parameter to Task.Delay so the delay can be interrupted early if cancellation is
requested. Change Task.Delay(100) to Task.Delay(100, request.CancellationToken)
in the debounce section to make the delay cancellation-aware.
- Around line 324-366: In the RefreshDataCoreAsync method, wrap the call to
ResolveItemsRequestAsync(request) in a try-catch block to explicitly handle
OperationCanceledException. When this exception is caught, it indicates the load
was superseded by a newer request, so ensure the cleanup logic (checking if this
load is still current and disposing the cancellation token source) still
executes properly before allowing the method to complete gracefully without
propagating the exception. This prevents noisy failures during rapid sort/page
refreshes and keeps the load-state cleanup paths robust.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Around line 83-87: The column resize logic does not enforce a minimum width
constraint on the nextWidth calculation, allowing columns to become negative or
excessively small during drag operations. Add a minimum width constraint by
clamping nextWidth to a reasonable minimum value (such as 20 or 30 pixels)
before assigning it to updatedColumnWidth and applying it to th.style.width.
This prevents column collapse and maintains layout stability during resizing
operations.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 139-196: The LoadServerData method updates serverLoading and
serverLastRequest at the end but does not trigger a final StateHasChanged call
to re-render the parent component. Since this is a callback function, these
state changes may render late or not at all. Wrap the lines that set
serverLoading to false and update serverLastRequest in a finally block, and add
an await InvokeAsync(StateHasChanged) call within that finally block to ensure
the parent component always re-renders after the data loading operation
completes.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs`:
- Around line 384-386: The Razor snippet in the
BitQuickGridDemo.razor.samples.cs file binds to `virtualSampleNameFilter` in the
`@bind-Value` attribute of the BitSearchBox component, but the corresponding C#
property is defined as `VirtualSampleNameFilter` in PascalCase. Update the
binding to use `VirtualSampleNameFilter` to match the actual property name
defined in the C# code-behind, ensuring the sample compiles correctly when
copied.
- Around line 1090-1093: The BitButton component at lines 1090-1093 that
controls row expansion lacks accessibility attributes. Add a dynamic AriaLabel
or Title attribute to the BitButton element that changes based on the expansion
state. The label should indicate whether clicking the button will expand or
collapse the row, similar to the implementation in the runtime demo. Reference
the expandedRowTemplateCodes collection to conditionally set the label, matching
the same logic used for the IconName attribute selection.

---

Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 89-92: In the BitDataGridDataProcessor.cs file where the path
variable is constructed using string interpolation with g.Key, replace the
simple string concatenation of g.Key with a typed, culture-invariant identifier
that prevents collisions between distinct keys producing identical text. Instead
of relying on the string representation of g.Key which can be culture-dependent
and cause identical display values to collide, use a robust uniqueness mechanism
such as the key's hash code or another unique type-safe identifier that
preserves the actual key identity across different key instances.

---

Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs`:
- Line 9: The `FormattedValue` property in the `BitDataGridAggregateResult`
class has a default value of `string.Empty` but is not marked as `required`,
creating an inconsistency with the `ColumnId` property which is required. To fix
this, either mark `FormattedValue` as `required` to match the pattern of
`ColumnId`, or change its type to `string?` and set the default value to `null`
to properly represent it as an optional nullable property. Choose the approach
that best represents the semantic intent of this property in your data model.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs`:
- Line 25: The BitDataGridCellEventArgs class has inconsistent use of the
required modifier. The Value property at line 25 and Mouse property at line 28
are not marked as required, while Item and Column properties are. Since the
construction code in BitDataGrid.razor.cs always explicitly sets all four
properties when creating new instances, either add the required modifier to the
Value and Mouse properties to match Item and Column for consistency and
compile-time safety, or add documentation comments explaining why these two
properties are intentionally optional despite always being set during
construction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e5869d38-2410-48a4-ad7d-364183e27bec

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and b9ba019.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts Outdated
@msynk

msynk commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs (1)

324-346: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure CTS cleanup also runs for the virtualized branch.

thisLoadCts cleanup is currently only guaranteed in the non-virtualized finally. If RefreshDataAsync() throws in the virtualized path, _pendingDataLoadCancellationTokenSource can stay non-null and undisposed.

Suggested minimal restructuring
 private async Task RefreshDataCoreAsync()
 {
     var previousCts = _pendingDataLoadCancellationTokenSource;
     if (previousCts is not null)
     {
         previousCts.Cancel();
         previousCts.Dispose();
     }
     var thisLoadCts = _pendingDataLoadCancellationTokenSource = new CancellationTokenSource();
-
-    if (_virtualizeComponent is not null)
-    {
-        await _virtualizeComponent.RefreshDataAsync();
-        if (ReferenceEquals(_pendingDataLoadCancellationTokenSource, thisLoadCts))
-        {
-            thisLoadCts.Dispose();
-            _pendingDataLoadCancellationTokenSource = null;
-        }
-    }
-    else
+    try
     {
-        _lastRefreshedPaginationStateHash = Pagination?.GetHashCode();
-        var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage);
-        var request = new BitQuickGridItemsProviderRequest<TGridItem>(
-            startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token);
-        try
+        if (_virtualizeComponent is not null)
         {
-            var result = await ResolveItemsRequestAsync(request);
-            if (!thisLoadCts.IsCancellationRequested)
-            {
-                _currentNonVirtualizedViewItems = result.Items;
-                _ariaBodyRowCount = _currentNonVirtualizedViewItems.Count;
-                await (Pagination?.SetTotalItemCountAsync(result.TotalItemCount) ?? Task.CompletedTask);
-            }
+            await _virtualizeComponent.RefreshDataAsync();
         }
-        catch (OperationCanceledException)
+        else
         {
-            // This load was superseded by a newer request; swallow the cancellation and fall through
-            // to the cleanup below so the load-state remains consistent.
+            _lastRefreshedPaginationStateHash = Pagination?.GetHashCode();
+            var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage);
+            var request = new BitQuickGridItemsProviderRequest<TGridItem>(
+                startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token);
+            var result = await ResolveItemsRequestAsync(request);
+            if (!thisLoadCts.IsCancellationRequested)
+            {
+                _currentNonVirtualizedViewItems = result.Items;
+                _ariaBodyRowCount = _currentNonVirtualizedViewItems.Count;
+                await (Pagination?.SetTotalItemCountAsync(result.TotalItemCount) ?? Task.CompletedTask);
+            }
         }
-        finally
-        {
-            if (ReferenceEquals(_pendingDataLoadCancellationTokenSource, thisLoadCts))
-            {
-                thisLoadCts.Dispose();
-                _pendingDataLoadCancellationTokenSource = null;
-            }
-        }
+    }
+    catch (OperationCanceledException) when (thisLoadCts.IsCancellationRequested)
+    {
+        // Superseded by a newer request.
+    }
+    finally
+    {
+        if (ReferenceEquals(_pendingDataLoadCancellationTokenSource, thisLoadCts))
+        {
+            _pendingDataLoadCancellationTokenSource = null;
+        }
+        thisLoadCts.Dispose();
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 324 - 346, The RefreshDataCoreAsync method's virtualized branch
does not guarantee cleanup of thisLoadCts if an exception is thrown during the
RefreshDataAsync() call on the _virtualizeComponent. The cleanup logic that
disposes thisLoadCts and clears _pendingDataLoadCancellationTokenSource (the
ReferenceEquals check) should be wrapped in a try-finally block around the
virtualized code path to ensure it always executes even when RefreshDataAsync()
throws, preventing resource leaks and keeping
_pendingDataLoadCancellationTokenSource properly managed.
🧹 Nitpick comments (7)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs (1)

323-364: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Reset loading in finally and honor cancellation in the server-read sample.

This sample can leave loading = true if an exception/cancellation happens before Line 362. Since BitDataGridReadRequest exposes CancellationToken, it should be used in the simulated async work as well.

Suggested snippet adjustment
 private async Task<BitDataGridReadResult<Product>> LoadData(BitDataGridReadRequest request)
 {
-    loading = true;
-    await Task.Delay(250); // simulate a backend round-trip
-
-    IEnumerable<Product> query = all;
-
-    // filtering
-    foreach (var f in request.Filters)
-    {
-        var term = f.Value?.ToString() ?? "";
-        query = f.ColumnId switch
-        {
-            nameof(Product.Name) => query.Where(p => p.Name.Contains(term, StringComparison.OrdinalIgnoreCase)),
-            nameof(Product.Price) => query.Where(p => p.Price.ToString().Contains(term)),
-            nameof(Product.Id) => query.Where(p => p.Id.ToString().Contains(term)),
-            _ => query
-        };
-    }
-
-    // sorting
-    var sort = request.Sorts.FirstOrDefault();
-    if (sort is not null)
-    {
-        Func<Product, object> key = sort.ColumnId switch
-        {
-            nameof(Product.Name) => p => p.Name,
-            nameof(Product.Price) => p => p.Price,
-            _ => p => p.Id
-        };
-        query = sort.Direction == BitDataGridSortDirection.Descending
-            ? query.OrderByDescending(key)
-            : query.OrderBy(key);
-    }
-
-    // paging
-    var filtered = query.ToList();
-    var items = filtered.Skip(request.Skip).Take(request.Take ?? filtered.Count).ToList();
-
-    loading = false;
-    return new BitDataGridReadResult<Product>(items, filtered.Count);
+    loading = true;
+    try
+    {
+        await Task.Delay(250, request.CancellationToken); // simulate a backend round-trip
+
+        IEnumerable<Product> query = all;
+
+        // filtering
+        foreach (var f in request.Filters)
+        {
+            var term = f.Value?.ToString() ?? "";
+            query = f.ColumnId switch
+            {
+                nameof(Product.Name) => query.Where(p => p.Name.Contains(term, StringComparison.OrdinalIgnoreCase)),
+                nameof(Product.Price) => query.Where(p => p.Price.ToString().Contains(term)),
+                nameof(Product.Id) => query.Where(p => p.Id.ToString().Contains(term)),
+                _ => query
+            };
+        }
+
+        // sorting
+        var sort = request.Sorts.FirstOrDefault();
+        if (sort is not null)
+        {
+            Func<Product, object> key = sort.ColumnId switch
+            {
+                nameof(Product.Name) => p => p.Name,
+                nameof(Product.Price) => p => p.Price,
+                _ => p => p.Id
+            };
+            query = sort.Direction == BitDataGridSortDirection.Descending
+                ? query.OrderByDescending(key)
+                : query.OrderBy(key);
+        }
+
+        // paging
+        var filtered = query.ToList();
+        var items = filtered.Skip(request.Skip).Take(request.Take ?? filtered.Count).ToList();
+        return new BitDataGridReadResult<Product>(items, filtered.Count);
+    }
+    finally
+    {
+        loading = false;
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`
around lines 323 - 364, The LoadData method sets loading to true but only resets
it to false at the end of the method, leaving it stuck at true if an exception
occurs. Additionally, the simulated async work via Task.Delay does not honor the
CancellationToken from the BitDataGridReadRequest. Wrap the method body in a
try-finally block and move the loading = false assignment to the finally block
to ensure it always executes regardless of exceptions. Also, pass
request.CancellationToken to the Task.Delay call to respect cancellation
requests.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor (2)

403-403: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

int.Parse could throw on malformed select value.

While the <select> only contains valid integer options, using int.Parse without validation is fragile. Consider int.TryParse for defensive coding.

♻️ Defensive parsing
-<select class="bit-dtg-page-size" `@onchange`="e => SetPageSizeAsync(int.Parse((string)e.Value!))">
+<select class="bit-dtg-page-size" `@onchange`="e => { if (int.TryParse((string?)e.Value, out var size)) _ = SetPageSizeAsync(size); }">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor` at
line 403, The select element's `@onchange` handler in the page size dropdown uses
int.Parse without error handling, which can throw exceptions if the value is
malformed. Replace int.Parse((string)e.Value!) with int.TryParse to safely
attempt the conversion and gracefully handle parsing failures. Check the result
of TryParse before calling SetPageSizeAsync, and only proceed if the conversion
succeeds.

27-31: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

CSV data URI export may fail for large datasets.

The CSV export uses a data:text/csv URI which has browser-specific length limits (typically 2MB in modern browsers, but varies). For grids with thousands of rows, this approach may silently truncate or fail.

Consider adding a fallback using Blob URLs or JS interop for larger exports if this becomes an issue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`
around lines 27 - 31, The CSV export functionality in the BitDataGrid component
uses a data:text/csv URI approach which has browser limitations (typically 2MB)
and may silently fail for large datasets. Modify the CSV export section to check
the size of the CSV data generated by the ToCsv() method, and if it exceeds a
reasonable threshold (around 1-2MB), implement a fallback approach using Blob
URLs or JavaScript interop to trigger the download instead of using the data
URI. This ensures large exports work reliably while maintaining backward
compatibility for smaller datasets that work fine with the current data URI
approach.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (3)

226-226: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

VisibleColumns allocates a new list on every access.

The property VisibleColumns calls .ToList() on every access, creating a new allocation each time. It's accessed frequently (header rendering, cell navigation, spanning, footer, etc.), causing unnecessary GC pressure.

♻️ Consider caching the visible columns list
+    private IReadOnlyList<BitDataGridColumn<TItem>>? _visibleColumnsCache;
+    private int _visibleColumnsVersion;
+
-    internal IReadOnlyList<BitDataGridColumn<TItem>> VisibleColumns => _columns.Where(c => c.Visible).ToList();
+    internal IReadOnlyList<BitDataGridColumn<TItem>> VisibleColumns
+    {
+        get
+        {
+            // Invalidate cache when columns change
+            if (_visibleColumnsCache is null || _visibleColumnsVersion != _columns.Count)
+            {
+                _visibleColumnsCache = _columns.Where(c => c.Visible).ToList();
+                _visibleColumnsVersion = _columns.Count;
+            }
+            return _visibleColumnsCache;
+        }
+    }

Also invalidate the cache in AddColumn, RemoveColumn, and SetColumnVisibilityAsync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs` at
line 226, The VisibleColumns property creates a new list allocation on every
access via .ToList(), causing unnecessary garbage collection. Cache the filtered
list result in a private field (e.g., _visibleColumnsCache) and update the
VisibleColumns property to return the cached value. Invalidate the cache by
resetting it to null in the AddColumn, RemoveColumn, and
SetColumnVisibilityAsync methods so the list is only recalculated when columns
are actually modified, not on every property access.

734-735: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Multiple enumerations over _pageItems for selection state.

AllPageSelected and SomePageSelected both call _pageItems.Where(CanSelectRow) multiple times, causing repeated enumeration. Consider caching the selectable items.

♻️ Suggested optimization
-    internal bool AllPageSelected => _pageItems.Where(CanSelectRow).Any() && _pageItems.Where(CanSelectRow).All(_selected.Contains);
-    internal bool SomePageSelected => _pageItems.Where(CanSelectRow).Any(_selected.Contains) && !AllPageSelected;
+    internal bool AllPageSelected
+    {
+        get
+        {
+            var selectable = _pageItems.Where(CanSelectRow).ToList();
+            return selectable.Count > 0 && selectable.All(_selected.Contains);
+        }
+    }
+
+    internal bool SomePageSelected
+    {
+        get
+        {
+            var selectable = _pageItems.Where(CanSelectRow).ToList();
+            return selectable.Any(_selected.Contains) && !selectable.All(_selected.Contains);
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 734 - 735, The AllPageSelected and SomePageSelected properties both
enumerate _pageItems.Where(CanSelectRow) multiple times within their respective
expressions, causing inefficient repeated filtering. Refactor both properties by
caching the filtered enumerable result (the result of
_pageItems.Where(CanSelectRow)) into a local variable first, then use that
cached variable in the subsequent checks with Any, All, and Contains operations
to avoid multiple enumerations.

1068-1072: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Unnecessary list allocation in ResolveColSpan.

cols.ToList().IndexOf(column) creates a temporary list just to find the index. Use a loop or LINQ instead.

♻️ Suggested fix
     internal int ResolveColSpan(BitDataGridColumn<TItem> column, TItem item)
     {
         if (column.ColSpan is null) return 1;
         var span = column.ColSpan(item) ?? 1;
         if (span < 1) span = 1;
         var cols = VisibleColumns;
-        var idx = cols.ToList().IndexOf(column);
+        var idx = -1;
+        for (int i = 0; i < cols.Count; i++)
+        {
+            if (cols[i] == column) { idx = i; break; }
+        }
         if (idx < 0) return 1;
         return Math.Min(span, cols.Count - idx);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 1068 - 1072, The ResolveColSpan method is creating an unnecessary
temporary list by calling cols.ToList() solely to use the IndexOf method for
finding the column's position. Replace this inefficient approach by either using
LINQ enumeration-based indexing without materialization, or iterating through
the cols collection with a counter to find the index position directly, avoiding
the allocation of the temporary list.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor (1)

24-31: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Select element has redundant value binding.

The <select> element has both value="@(GetString())" and each <option> has selected="@(name == GetString())". The Blazor value binding on <select> should suffice; the explicit selected attribute is redundant and can cause inconsistent behavior in some browsers.

♻️ Suggested simplification
-        <select class="bit-dtg-editor" value="@(GetString())" `@onchange`="e => Set(e.Value)">
+        <select class="bit-dtg-editor" `@bind`="`@_enumValue`" `@bind`:event="onchange" `@bind`:after="() => Set(_enumValue)">
             `@foreach` (var name in Enum.GetNames(Column.Accessor!.UnderlyingType))
             {
-                <option value="`@name`" selected="@(name == GetString())">`@name`</option>
+                <option value="`@name`">`@name`</option>
             }
         </select>

Or simply remove the selected attribute since value on <select> handles selection:

-                <option value="`@name`" selected="@(name == GetString())">`@name`</option>
+                <option value="`@name`">`@name`</option>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`
around lines 24 - 31, The Enum case in the BitDataGridCellEditor.razor component
has redundant selection binding on the select element. Remove the explicit
selected="@(name == GetString())" attribute from each option element within the
foreach loop, as the value binding on the parent select element with
value="@(GetString())" already handles selecting the correct option in Blazor.
Keep only the value binding on the select element for proper Blazor two-way
binding behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts`:
- Around line 11-18: The invokeMethodAsync call in the check function lacks
error handling for promise rejections that may occur if the Blazor circuit
disconnects between the disposed flag check and the async method invocation. Add
a .catch() handler to the
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync') call to gracefully
handle potential promise rejections and prevent unhandled rejection errors in
the console during circuit disconnects or navigation.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 201-219: The filter evaluation in the method has incorrect null
comparison semantics. The early return on line 201-202 when filter.Value is null
causes all rows to match, which is incorrect for Equals and NotEquals operators.
Additionally, when the row value itself is null, null values are passed to the
comparer which can cause incorrect matches for LessThan and LessThanOrEqual
operators. Remove the early return for null filter values, and instead add
proper null handling logic that checks if the row value is null separately
before calling BitDataGridValueComparer.Instance.Compare(). When the row value
is null, return false for comparison operators (GreaterThan, GreaterThanOrEqual,
LessThan, LessThanOrEqual) and handle Equals/NotEquals based on whether the
filter value is also null.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 89-90: The Split call in BitDataGridPropertyAccessor.cs uses
StringSplitOptions.RemoveEmptyEntries which silently normalizes malformed paths
like "." and "Address..City" instead of rejecting them. Remove the
RemoveEmptyEntries option from the Split call and add validation logic to check
if any segments are empty or whitespace, then throw an appropriate exception
(such as ArgumentException) if found, ensuring that only valid property paths
are processed and preventing silent misbinding in sort/filter/edit operations.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 411-414: The ProvideVirtualizedItems method calls
ResolveItemsRequestAsync which can throw OperationCanceledException when the
virtualized request is superseded by a newer request after debounce. Wrap the
ResolveItemsRequestAsync call in a try-catch block to explicitly catch and
handle OperationCanceledException so it does not propagate out of the
ProvideVirtualizedItems method. Handle the exception appropriately by returning
an empty or default result set that the virtualization system expects.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 215-231: The LoadMore method currently applies only the first sort
descriptor from request.Sorts by using FirstOrDefault(), which causes
multi-column sorts to be ignored. Replace the single sort application logic with
a loop that iterates through all items in request.Sorts (if any exist) and
applies each sort descriptor sequentially to the query. Reuse the key selector
switch statement that maps sort.ColumnId to the Product properties for each sort
descriptor, applying OrderByDescending or OrderBy based on each sort's Direction
property to maintain proper multi-sort ordering.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 324-346: The RefreshDataCoreAsync method's virtualized branch does
not guarantee cleanup of thisLoadCts if an exception is thrown during the
RefreshDataAsync() call on the _virtualizeComponent. The cleanup logic that
disposes thisLoadCts and clears _pendingDataLoadCancellationTokenSource (the
ReferenceEquals check) should be wrapped in a try-finally block around the
virtualized code path to ensure it always executes even when RefreshDataAsync()
throws, preventing resource leaks and keeping
_pendingDataLoadCancellationTokenSource properly managed.

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Line 403: The select element's `@onchange` handler in the page size dropdown
uses int.Parse without error handling, which can throw exceptions if the value
is malformed. Replace int.Parse((string)e.Value!) with int.TryParse to safely
attempt the conversion and gracefully handle parsing failures. Check the result
of TryParse before calling SetPageSizeAsync, and only proceed if the conversion
succeeds.
- Around line 27-31: The CSV export functionality in the BitDataGrid component
uses a data:text/csv URI approach which has browser limitations (typically 2MB)
and may silently fail for large datasets. Modify the CSV export section to check
the size of the CSV data generated by the ToCsv() method, and if it exceeds a
reasonable threshold (around 1-2MB), implement a fallback approach using Blob
URLs or JavaScript interop to trigger the download instead of using the data
URI. This ensures large exports work reliably while maintaining backward
compatibility for smaller datasets that work fine with the current data URI
approach.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Line 226: The VisibleColumns property creates a new list allocation on every
access via .ToList(), causing unnecessary garbage collection. Cache the filtered
list result in a private field (e.g., _visibleColumnsCache) and update the
VisibleColumns property to return the cached value. Invalidate the cache by
resetting it to null in the AddColumn, RemoveColumn, and
SetColumnVisibilityAsync methods so the list is only recalculated when columns
are actually modified, not on every property access.
- Around line 734-735: The AllPageSelected and SomePageSelected properties both
enumerate _pageItems.Where(CanSelectRow) multiple times within their respective
expressions, causing inefficient repeated filtering. Refactor both properties by
caching the filtered enumerable result (the result of
_pageItems.Where(CanSelectRow)) into a local variable first, then use that
cached variable in the subsequent checks with Any, All, and Contains operations
to avoid multiple enumerations.
- Around line 1068-1072: The ResolveColSpan method is creating an unnecessary
temporary list by calling cols.ToList() solely to use the IndexOf method for
finding the column's position. Replace this inefficient approach by either using
LINQ enumeration-based indexing without materialization, or iterating through
the cols collection with a counter to find the index position directly, avoiding
the allocation of the temporary list.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`:
- Around line 24-31: The Enum case in the BitDataGridCellEditor.razor component
has redundant selection binding on the select element. Remove the explicit
selected="@(name == GetString())" attribute from each option element within the
foreach loop, as the value binding on the parent select element with
value="@(GetString())" already handles selecting the correct option in Blazor.
Keep only the value binding on the select element for proper Blazor two-way
binding behavior.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 323-364: The LoadData method sets loading to true but only resets
it to false at the end of the method, leaving it stuck at true if an exception
occurs. Additionally, the simulated async work via Task.Delay does not honor the
CancellationToken from the BitDataGridReadRequest. Wrap the method body in a
try-finally block and move the loading = false assignment to the finally block
to ensure it always executes regardless of exceptions. Also, pass
request.CancellationToken to the Task.Delay call to respect cancellation
requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 181acf5c-8519-490b-bb7d-03de1ca65181

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and a44c223.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
@msynk

msynk commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs (1)

13-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting common generation logic to reduce duplication.

The Generate and GeneratePersian methods duplicate the entire Product generation algorithm. Only the string arrays differ (adjectives, nouns, suppliers). Extracting the common logic into a shared helper would improve maintainability and ensure consistency when updating the generation algorithm.

♻️ Refactoring approach

Extract a private helper that accepts the string arrays as parameters:

+    private static List<Product> GenerateCore(
+        int count,
+        int seed,
+        string[] adjectives,
+        string[] nouns,
+        string[] suppliers)
+    {
+        var rng = new Random(seed);
+        var categories = Enum.GetValues<Category>();
+        var list = new List<Product>(count);
+        var referenceDate = new DateTime(2024, 1, 1);
+        for (int i = 1; i <= count; i++)
+        {
+            list.Add(new Product
+            {
+                Id = i,
+                Name = $"{adjectives[rng.Next(adjectives.Length)]} {nouns[rng.Next(nouns.Length)]} {rng.Next(100, 999)}",
+                Category = categories[rng.Next(categories.Length)],
+                Price = Math.Round((decimal)(rng.NextDouble() * 990 + 5), 2),
+                Stock = rng.Next(0, 500),
+                Rating = Math.Round(rng.NextDouble() * 4 + 1, 1),
+                Discontinued = rng.Next(0, 5) == 0,
+                ReleaseDate = referenceDate.AddDays(-rng.Next(0, 2000)),
+                Supplier = suppliers[rng.Next(suppliers.Length)]
+            });
+        }
+        return list;
+    }
+
     public static List<Product> Generate(int count, int seed = 42)
-    {
-        var rng = new Random(seed);
-        var categories = Enum.GetValues<Category>();
-        var list = new List<Product>(count);
-        var referenceDate = new DateTime(2024, 1, 1);
-        for (int i = 1; i <= count; i++)
-        {
-            list.Add(new Product
-            {
-                Id = i,
-                Name = $"{Adjectives[rng.Next(Adjectives.Length)]} {Nouns[rng.Next(Nouns.Length)]} {rng.Next(100, 999)}",
-                Category = categories[rng.Next(categories.Length)],
-                Price = Math.Round((decimal)(rng.NextDouble() * 990 + 5), 2),
-                Stock = rng.Next(0, 500),
-                Rating = Math.Round(rng.NextDouble() * 4 + 1, 1),
-                Discontinued = rng.Next(0, 5) == 0,
-                ReleaseDate = referenceDate.AddDays(-rng.Next(0, 2000)),
-                Supplier = Suppliers[rng.Next(Suppliers.Length)]
-            });
-        }
-        return list;
-    }
+        => GenerateCore(count, seed, Adjectives, Nouns, Suppliers);

     public static List<Product> GeneratePersian(int count, int seed = 42)
-    {
-        var rng = new Random(seed);
-        var categories = Enum.GetValues<Category>();
-        var list = new List<Product>(count);
-        var referenceDate = new DateTime(2024, 1, 1);
-        for (int i = 1; i <= count; i++)
-        {
-            list.Add(new Product
-            {
-                Id = i,
-                Name = $"{PersianAdjectives[rng.Next(PersianAdjectives.Length)]} {PersianNouns[rng.Next(PersianNouns.Length)]} {rng.Next(100, 999)}",
-                Category = categories[rng.Next(categories.Length)],
-                Price = Math.Round((decimal)(rng.NextDouble() * 990 + 5), 2),
-                Stock = rng.Next(0, 500),
-                Rating = Math.Round(rng.NextDouble() * 4 + 1, 1),
-                Discontinued = rng.Next(0, 5) == 0,
-                ReleaseDate = referenceDate.AddDays(-rng.Next(0, 2000)),
-                Supplier = PersianSuppliers[rng.Next(PersianSuppliers.Length)]
-            });
-        }
-        return list;
-    }
+        => GenerateCore(count, seed, PersianAdjectives, PersianNouns, PersianSuppliers);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs`
around lines 13 - 68, The Generate and GeneratePersian methods contain duplicate
code for the entire Product generation algorithm, differing only in which string
arrays they use. Create a private helper method that accepts three string array
parameters (for adjectives, nouns, and suppliers) and contains the common
generation logic currently duplicated in both methods. Then refactor the
Generate method to call this helper with Adjectives, Nouns, and Suppliers
arrays, and the GeneratePersian method to call it with PersianAdjectives,
PersianNouns, and PersianSuppliers arrays. This will eliminate duplication while
maintaining the same external behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 456-498: The LoadNextBatchAsync method awaits the OnLoadMore
callback which receives a cancellation token from ResetLoadCancellation(). If
the provider honors the cancellation token, it will throw an
OperationCanceledException that breaks the component flow. Wrap the await
statement for OnLoadMore in a try-catch block to catch
OperationCanceledException and handle it gracefully by returning early, since
cancellation is expected behavior when requests are superseded by newer loads.
This same fix should also be applied to any other similar load methods mentioned
in the "Also applies to" section that use ResetLoadCancellation() tokens.
- Around line 1199-1212: The Escape static method within the ToCsv() method
currently only handles CSV delimiter escaping but does not prevent formula
injection. When cell values begin with =, +, -, or @, spreadsheet applications
can interpret them as formulas when the CSV file is opened. Modify the Escape
static method to detect if the input value starts with any of these dangerous
characters and prepend a single quote to prevent formula execution, in addition
to the existing CSV delimiter escaping logic.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor`:
- Line 13: The `@onkeydown`="HandleKeyDown" binding at line 13 does not prevent
the default browser keyboard behavior, which can conflict with the grid
navigation logic implemented in the HandleKeyDown method (referenced around line
70). Modify the onkeydown event binding to include preventDefault:true parameter
to suppress native browser key handling and ensure the custom grid navigation
logic executes exclusively without browser defaults interfering with the
cell-navigation behavior.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`:
- Around line 24-27: The enum editor case in BitDataGridCellEditor.razor does
not validate that Column.Accessor exists and that its UnderlyingType is actually
an enum before attempting to call Enum.GetNames on it. Add a guard condition
before the select element that checks whether Column.Accessor is not null and
whether Column.Accessor.UnderlyingType is a valid enum type using Type.IsEnum.
If the guard condition fails, render a fallback editor instead of the enum
select dropdown to prevent null reference exceptions and invalid type operations
at runtime.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 36-40: The ConvertValue method (referenced around lines 68-70) is
silently coercing conversion failures to default values for non-nullable
properties, which causes invalid user input to be silently overwritten with
defaults like 0. Instead of silently returning default(T) on conversion failure,
modify ConvertValue to throw an exception or return a result that indicates
conversion failure, then update the SetValue method to handle this exception or
failure result appropriately by either not setting the value or propagating the
error to the caller so users are aware their invalid input was rejected rather
than silently converted.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 302-306: The call to _js.BitQuickGridCheckColumnOptionsPosition on
line 305 discards the returned ValueTask using the discard pattern instead of
awaiting it. Since the containing method is already async, replace the line that
discards the result with `await
_js.BitQuickGridCheckColumnOptionsPosition(_tableReference);` to properly await
the interop call and ensure positioning errors are not hidden.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor`:
- Around line 156-161: The Math.Round(p.Rating) call on line 160 uses banker's
rounding (midpoint-to-even), which causes inconsistent star rendering when the
rating is exactly at a midpoint value like 4.5. To fix this, replace
Math.Round(p.Rating) with Math.Round(p.Rating, MidpointRounding.AwayFromZero) to
ensure values round away from zero, providing more intuitive and consistent
visual feedback in the star rating display within the span element.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 139-144: The LoadServerData method and other async loader methods
do not honor the CancellationToken provided in BitDataGridReadRequest, allowing
superseded requests to complete and update stale UI state. Pass
request.CancellationToken to the Task.Delay() calls, and check for cancellation
(using ThrowIfCancellationRequested or similar checks) before updating UI state
variables like serverLoading, serverLastRequest, and infiniteLog to prevent
stale data from being written when a newer request supersedes the current one.
Apply this fix to all async loader methods mentioned, including LoadServerData
and any other similar loaders at the specified locations.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 346-358: The sort logic in this sample applies only the first sort
descriptor using FirstOrDefault() and ignores all additional sorts. Instead of
handling only a single sort, iterate through all items in request.Sorts and
chain the sort operations using OrderBy/OrderByDescending for the first sort and
ThenBy/ThenByDescending for subsequent sorts based on their respective column
IDs and directions. Apply this same complete sorting logic to both the initial
query sorting section (around the switch statement with Product.Name,
Product.Price cases) and the LoadMore method (lines 382-386) which currently
ignores request.Sorts entirely, ensuring consistent sort semantics are applied
in both code paths.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs`:
- Around line 629-630: The controller route definitions at lines 629, 736, 810,
and 904 use the attribute Route with pattern "[controller]/[action]" but the
client sample code expects routes prefixed with "api/". Update the Route
attribute in all four controller definitions (ProductsController and other
similar controllers) to include the "api/" prefix in the route pattern, changing
"[controller]/[action]" to "api/[controller]/[action]" to ensure consistency
between the server-side sample and the client-side usage.

---

Nitpick comments:
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs`:
- Around line 13-68: The Generate and GeneratePersian methods contain duplicate
code for the entire Product generation algorithm, differing only in which string
arrays they use. Create a private helper method that accepts three string array
parameters (for adjectives, nouns, and suppliers) and contains the common
generation logic currently duplicated in both methods. Then refactor the
Generate method to call this helper with Adjectives, Nouns, and Suppliers
arrays, and the GeneratePersian method to call it with PersianAdjectives,
PersianNouns, and PersianSuppliers arrays. This will eliminate duplication while
maintaining the same external behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ca5f25fb-53db-4b03-bab4-dc6a2e8df316

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 8a11d7c.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor Outdated
@msynk

msynk commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

@msynk Sure, I'll kick off a fresh review of the PR now!

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs (1)

141-147: 🩺 Stability & Availability | 🟠 Major

Move the cancellable delay inside try so cleanup always remains in scope.

At Line 143, await Task.Delay(250, request.CancellationToken) can throw before the try block at Line 146, causing the finally cleanup (Lines 198–209) to be bypassed and leaving serverLoading in an inconsistent state.

Proposed fix
 private async Task<BitDataGridReadResult<Product>> LoadServerData(BitDataGridReadRequest request)
 {
     serverLoading = true;
     await InvokeAsync(StateHasChanged);
-    await Task.Delay(250, request.CancellationToken);

     int total = 0;
     try
     {
+        await Task.Delay(250, request.CancellationToken);
+
         IEnumerable<Product> query = serverAll;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`
around lines 141 - 147, The cancellable Task.Delay on line 143 can throw a
cancellation exception before entering the try block that starts on line 146,
which causes the finally cleanup block to be bypassed and leaves serverLoading
in an inconsistent state. Move the await Task.Delay(250,
request.CancellationToken) statement inside the try block so that the finally
cleanup block (lines 198-209) will always execute regardless of whether the
delay is cancelled, ensuring serverLoading is properly reset.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (1)

1226-1235: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Quote CSV fields containing carriage returns.

Line 1233 quotes \n but not \r, so CR-only values can split rows in spreadsheet parsers. Include \r in the quoting condition.

Suggested fix
-            return v.Contains(',') || v.Contains('"') || v.Contains('\n')
+            return v.Contains(',') || v.Contains('"') || v.Contains('\n') || v.Contains('\r')
                 ? "\"" + v.Replace("\"", "\"\"") + "\""
                 : v;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 1226 - 1235, The Escape method checks for newline characters (\n)
when deciding whether to quote CSV fields, but it does not include carriage
return characters (\r) in the quoting condition. Update the return statement in
the Escape method to also check for \r using an additional .Contains('\r')
condition alongside the existing checks for comma, double quote, and newline, so
that fields containing carriage returns are properly quoted to prevent row
splitting in spreadsheet parsers.
🧹 Nitpick comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs (1)

4-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add XML documentation to properties.

All properties lack documentation summaries, reducing IntelliSense discoverability.

📝 Proposed XML documentation additions
 public sealed class BitDataGridFilterDescriptor
 {
+    /// <summary>Identifier of the column being filtered.</summary>
     public required string ColumnId { get; init; }
+    /// <summary>The filter operation to apply (e.g., Contains, Equals, GreaterThan).</summary>
     public BitDataGridFilterOperator Operator { get; set; } = BitDataGridFilterOperator.Contains;
+    /// <summary>The filter value to compare against, or null for operators like IsNull.</summary>
     public object? Value { get; set; }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs`
around lines 4 - 9, The BitDataGridFilterDescriptor class properties (ColumnId,
Operator, and Value) lack XML documentation comments, which prevents
IntelliSense from displaying helpful information for developers using these
properties. Add XML documentation summary comments above each property using the
standard /// <summary> syntax to describe what each property represents and its
purpose in the filter descriptor context.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs (1)

4-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add XML documentation to properties for IntelliSense clarity.

All properties lack individual doc summaries. Even simple DTOs benefit from documentation that appears in IDE tooltips and API docs.

📝 Proposed XML documentation additions
 public sealed class BitDataGridAggregateResult
 {
+    /// <summary>Identifier of the column this aggregate result applies to.</summary>
     public required string ColumnId { get; init; }
+    /// <summary>The type of aggregate function applied (sum, average, count, etc.).</summary>
     public BitDataGridAggregateType Type { get; init; }
+    /// <summary>The raw computed aggregate value, or null if computation produced no result.</summary>
     public object? Value { get; init; }
+    /// <summary>The formatted string representation of the aggregate value for display.</summary>
     public required string FormattedValue { get; init; }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs`
around lines 4 - 10, The BitDataGridAggregateResult class and its properties are
missing XML documentation comments that would provide IntelliSense clarity in
IDEs and API documentation. Add XML documentation comments above each property
(ColumnId, Type, Value, and FormattedValue) in the BitDataGridAggregateResult
class using the summary tags to describe what each property represents, its
purpose, and any relevant details about its usage or constraints.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs (1)

10-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add XML documentation to undocumented properties for better IntelliSense discoverability.

Properties ColumnId, KeyText, Aggregates, and the computed property HasSubGroups lack XML documentation summaries, which reduces discoverability in IntelliSense and impacts documentation generation. Lines 14 and 30 demonstrate the pattern already in use.

📝 Proposed XML documentation additions
     public sealed class BitDataGridGroup<TItem>
     {
+        /// <summary>Identifier of the column used for grouping.</summary>
         public required string ColumnId { get; init; }
+        /// <summary>The grouping key value for this group.</summary>
         public required object? Key { get; init; }
+        /// <summary>Human-readable text representation of the grouping key.</summary>
         public string KeyText { get; init; } = string.Empty;
 
         /// <summary>Zero-based nesting depth (0 = top level).</summary>
         public int Level { get; init; }
 
         /// <summary>Stable, unique path identifying this group across the whole tree (used for collapse state).</summary>
         public required string Path { get; init; }
 
         /// <summary>All rows that fall under this group (across nested subgroups).</summary>
         public List<TItem> Items { get; init; } = new();
 
         /// <summary>Child groups when this group is further grouped; empty for leaf groups.</summary>
         public List<BitDataGridGroup<TItem>> SubGroups { get; init; } = new();
 
+        /// <summary>Computed aggregates (e.g., sum, average) for columns in this group.</summary>
         public List<BitDataGridAggregateResult> Aggregates { get; init; } = new();
 
+        /// <summary>Indicates whether this group contains child groups (subgroups).</summary>
         public bool HasSubGroups => SubGroups.Count > 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs`
around lines 10 - 26, The properties ColumnId, KeyText, and Aggregates in the
BitDataGridGroup class are missing XML documentation summaries, which reduces
IntelliSense discoverability. Add XML documentation comments above each of these
properties following the same pattern already used for Level, Path, Items, and
SubGroups properties. Each summary should provide a brief description of what
the property represents in the context of data grid grouping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 124-141: The span element with the "bit-dtg-htext" class that
handles OnHeaderClick is not keyboard accessible because it lacks keyboard event
handling. Add a keyboard event handler (such as `@onkeydown`) to this span element
that triggers the sort action when Enter or Space keys are pressed. Create a new
event handler method (such as OnHeaderKeyDown) that checks if the pressed key is
Enter or Space and calls the appropriate sorting method to enable keyboard users
to activate column sorting.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 488-493: The catch block for OperationCanceledException is too
broad and swallows all operation cancellations, including unrelated ones or
provider-side timeouts. Instead of catching all OperationCanceledException
instances, you need to filter the catch block to only handle cancellations from
the grid's own request token. Add a condition inside the catch block or use a
filtered catch pattern to check if the cancellation exception's
CancellationToken property matches the expected grid request token (identify the
token field used for this load operation). If the cancellation is not from the
grid's own token, rethrow the exception so legitimate errors surface. Apply this
same filtering pattern to both occurrences mentioned (lines 488-493 and
583-588).
- Around line 433-448: In the ResetInfiniteAsync method, bump the load version
before awaiting the scrollToTop call to prevent race conditions where older
batches complete during the await and append stale rows to the freshly reset
list. The load version should be incremented immediately after clearing
_infiniteItems and before the try block that awaits
_infiniteHandle.InvokeVoidAsync("scrollToTop"), so that when
ResetLoadCancellation is called later in LoadNextBatchAsync, the version check
will correctly reject any stale batches that completed during the await window.
- Around line 280-289: The inputsChanged check in the BitDataGrid parameter
update logic only compares the Items reference using ReferenceEquals, which
fails to detect when a parent mutates the same collection instance in place
(adding/removing items without changing the reference). This leaves the cached
_view data stale in client-mode scenarios. Modify the inputsChanged condition to
include an additional check that forces a refresh in client-mode data scenarios
even when the Items reference remains the same, while keeping the existing
reference guard for server and infinite scroll modes. This ensures that
RefreshAsync is called when the actual collection contents may have changed.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs`:
- Around line 1-9: Add XML documentation summaries for the three properties in
the BitDataGridFilterDescriptor class. For the ColumnId property, document that
it represents the identifier of the column being filtered and note that it is
immutable (required init property). For the Operator property, document its
purpose as specifying the filter operation and mention that it defaults to
BitDataGridFilterOperator.Contains. For the Value property, document that it
represents the filter value and note that it is nullable, explaining that its
meaning depends on the selected operator (e.g., not used for IsEmpty or
IsNotEmpty operators).

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 388-391: The LoadMore method's Task.Delay call does not respect
the cancellation token provided in the BitDataGridReadRequest parameter. Update
the await Task.Delay(350) statement to pass request.CancellationToken as the
second parameter to Task.Delay so that the sample code correctly honors
cancellation requests, matching the actual implementation behavior.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 4: The `@using` directive on line 4 of BitQuickGridDemo.razor imports from
Demo.Shared.Dtos.QuickGridDemo, but this namespace does not exist. The actual
namespace is Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo as used in the
code-behind file BitQuickGridDemo.razor.cs. Update the `@using` statement to use
the full namespace Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo instead of the
abbreviated Demo.Shared.Dtos.QuickGridDemo to match the correct fully-qualified
namespace and allow the code to compile.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 1226-1235: The Escape method checks for newline characters (\n)
when deciding whether to quote CSV fields, but it does not include carriage
return characters (\r) in the quoting condition. Update the return statement in
the Escape method to also check for \r using an additional .Contains('\r')
condition alongside the existing checks for comma, double quote, and newline, so
that fields containing carriage returns are properly quoted to prevent row
splitting in spreadsheet parsers.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 141-147: The cancellable Task.Delay on line 143 can throw a
cancellation exception before entering the try block that starts on line 146,
which causes the finally cleanup block to be bypassed and leaves serverLoading
in an inconsistent state. Move the await Task.Delay(250,
request.CancellationToken) statement inside the try block so that the finally
cleanup block (lines 198-209) will always execute regardless of whether the
delay is cancelled, ensuring serverLoading is properly reset.

---

Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs`:
- Around line 10-26: The properties ColumnId, KeyText, and Aggregates in the
BitDataGridGroup class are missing XML documentation summaries, which reduces
IntelliSense discoverability. Add XML documentation comments above each of these
properties following the same pattern already used for Level, Path, Items, and
SubGroups properties. Each summary should provide a brief description of what
the property represents in the context of data grid grouping.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs`:
- Around line 4-10: The BitDataGridAggregateResult class and its properties are
missing XML documentation comments that would provide IntelliSense clarity in
IDEs and API documentation. Add XML documentation comments above each property
(ColumnId, Type, Value, and FormattedValue) in the BitDataGridAggregateResult
class using the summary tags to describe what each property represents, its
purpose, and any relevant details about its usage or constraints.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs`:
- Around line 4-9: The BitDataGridFilterDescriptor class properties (ColumnId,
Operator, and Value) lack XML documentation comments, which prevents
IntelliSense from displaying helpful information for developers using these
properties. Add XML documentation summary comments above each property using the
standard /// <summary> syntax to describe what each property represents and its
purpose in the filter descriptor context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5c2fdcdd-b19e-430a-8c6b-b05b04b60e5f

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 08a958f.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

♻️ Duplicate comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs (1)

305-317: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rebind when ResizableColumns flips, not just when the column hash changes.

Line 312 only looks at _columns, so false -> true (or true -> false -> true) with the same columns never calls BitQuickGridInit(...). The new .bit-qkg-drg handles render, but resizing stays inert.

Suggested fix
+    private bool _lastRenderHadResizableColumns;
+
     protected override async Task OnAfterRenderAsync(bool firstRender)
     {
         if (firstRender)
         {
             _jsEventDisposable = await _js.BitQuickGridInit(_tableReference);
             _lastInitColumnsHash = ComputeColumnsHash();
+            _lastRenderHadResizableColumns = ResizableColumns;
         }
-        else if (ResizableColumns)
+        else
         {
-            var hash = ComputeColumnsHash();
-            if (hash != _lastInitColumnsHash)
+            var hash = ComputeColumnsHash();
+            var shouldRebind = ResizableColumns
+                && (hash != _lastInitColumnsHash || _lastRenderHadResizableColumns is false);
+
+            _lastInitColumnsHash = hash;
+            _lastRenderHadResizableColumns = ResizableColumns;
+
+            if (shouldRebind)
             {
-                _lastInitColumnsHash = hash;
                 await StopJsEventsAsync();
                 _jsEventDisposable = await _js.BitQuickGridInit(_tableReference);
             }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 305 - 317, The resize initialization in BitQuickGrid.razor.cs only
re-runs when ComputeColumnsHash() changes, so toggling ResizableColumns on and
off with the same columns leaves the new .bit-qkg-drg handles unbound. Update
the logic around the _lastInitColumnsHash / _jsEventDisposable flow in the
render path so BitQuickGridInit(...) also runs when ResizableColumns changes
state, not just when the column hash differs, and make sure the previous JS
registration is stopped before rebinding.
src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss (1)

4-5: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove .scss extensions from @import paths.

The .scss extensions violate the scss/load-partial-extension stylelint rule and will fail CI linting.

-@import "../Components/QuickGrid/BitQuickGrid.scss";
-@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss";
+@import "../Components/QuickGrid/BitQuickGrid";
+@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss` around lines 4
- 5, The `@import` statements in extra-components.scss use explicit .scss
extensions, which violates the scss/load-partial-extension rule. Update the
imports to reference the partials without the extension, using the existing
BitQuickGrid and BitQuickGridPaginator paths so the stylesheet continues to
resolve correctly and passes linting.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor (1)

4-4: ⚠️ Potential issue | 🔴 Critical

Fix the DTO namespace import.

Line 4 still imports Demo.Shared.Dtos.QuickGridDemo, while the moved DTOs and this page's code-behind use Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo. This Razor file will not resolve the QuickGrid demo DTOs until the import matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`
at line 4, The QuickGrid demo Razor page still imports the old DTO namespace, so
update the `@using` in BitQuickGridDemo.razor to match the moved DTOs used by the
code-behind: Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo instead of
Demo.Shared.Dtos.QuickGridDemo. Make the namespace import consistent with the
demo DTO types so the page can resolve the QuickGrid demo models correctly.
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs (1)

46-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid keeping the silent-default conversion path public.

SetValue now correctly rejects failed coercions, but ConvertValue still maps the same failures to default(T). That makes the new public API easy to misuse in future edit/filter code and can reintroduce silent 0/false/DateTime.MinValue writes. Prefer exposing TryConvertValue alone, or rename this helper to something explicit like ConvertOrDefault.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`
around lines 46 - 48, The public BitDataGridPropertyAccessor.ConvertValue helper
still hides failed coercions by returning the property default, which can be
misused by future edit/filter flows. Update the BitDataGridPropertyAccessor API
so callers use TryConvertValue for failure-aware handling, or make the fallback
behavior explicit by renaming ConvertValue to something like ConvertOrDefault
and keeping it out of the normal public path; ensure SetValue and any other
callers rely on the safer conversion result instead of silent defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 234-240: The data mode predicates in BitDataGrid are currently
independent, so supplying both OnRead and OnLoadMore lets RefreshAsync behave
like infinite loading while TotalCount/TotalPages still use server mode. Update
the mode detection logic around IsServerMode, IsInfiniteMode, RefreshAsync, and
any initialization/parameter validation so these modes are mutually exclusive,
and reject or surface an error when both callbacks are set. Ensure paging,
count, and ARIA state all come from a single consistent mode.
- Around line 285-306: The selection update logic in BitDataGrid.razor.cs can
emit a cleared selection to the parent before the incoming SelectedItems are
applied, and it also bypasses SelectionMode constraints. Update the
selection-sync branch around the mode-change handling so SelectedItems is
normalized to the current SelectionMode first (for example, respect Single and
None before copying into _selected), then notify only when there is no
controlled incoming selection to apply. Keep the fix centered on the selection
sync path that uses _lastSelectionMode, SelectionMode, SelectedItems, and
NotifySelectionAsync.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor`:
- Around line 13-14: The edit-mode key handling in BitDataGridCell is too broad:
ShouldPreventKeyDefault currently disables preventDefault for the whole edit
state, so Enter and Escape still run native browser actions alongside
HandleKeyDown/CommitEditAsync. Update ShouldPreventKeyDefault to be key-aware
and keep default suppression enabled for the grid-owned keys (Enter and Escape)
even while editing, while leaving other keys unaffected so normal typing still
works.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`:
- Around line 24-30: The enum editor in BitDataGridCellEditor.razor is dropping
nullable values because GetString() can return an empty string but the select
only offers concrete enum names. Update the enum branch in the cell editor to
detect nullable enum targets via Column.Accessor.UnderlyingType and render an
empty option when the current value is null or the member type is nullable, and
ensure Set(e.Value) can map that empty selection back to null instead of the
first enum member.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs`:
- Around line 120-132: The column registration in
BitDataGridColumn<TItem>.OnInitialized is happening before Accessor is
populated, so aggregate columns can be missed during Grid.AddColumn(this) when
footers are recomputed immediately. Move the Accessor initialization logic into
OnInitialized before the Grid.AddColumn(this) call, or otherwise ensure Accessor
is set first for HasField columns, while keeping OnParametersSet consistent for
later parameter updates.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`:
- Around line 28-30: The row-selection checkbox in BitDataGridRow.razor is
unlabeled for assistive tech. Update the checkbox inside the row-selection cell
to include an accessible name using aria-label or aria-labelledby, ideally with
row-specific context from the current Item/Grid so screen readers can identify
which row is being selected. Keep the change localized to the checkbox markup in
BitDataGridRow and preserve the existing selection behavior.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs`:
- Around line 3-11: Add a separate data type for time-bearing values in
BitDataGridColumnDataType so DateOnly can stay on the Date path while DateTime
and DateTimeOffset can be handled distinctly. Update
BitDataGridColumn<TItem>.EffectiveDataType to map these types to the new enum
value instead of Date, and adjust BitDataGridCellEditor to render an appropriate
input/editor for the new case rather than always using the date-only control.
Make sure the new branch preserves time and offset semantics for editable
columns.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs`:
- Around line 4-17: Add an explicit unset value to BitDataGridFilterOperator so
the default enum value does not map to a real filter operator; introduce a
None/Unspecified member at 0 and shift the existing operators down accordingly,
then update any code that reads or serializes BitDataGridFilterOperator or
consumes it in the DataGrid filter descriptor path to treat the unset value as
invalid or omitted rather than as Contains.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs`:
- Around line 12-13: `BitDataGridRowReorderEventArgs` currently marks
`FromIndex` and `ToIndex` as required even though `OnRowReorder` can emit `-1`
sentinels from `BitDataGrid.razor.cs`; update this model to represent
unavailable indices explicitly, preferably by making `FromIndex` and `ToIndex`
nullable, or otherwise document the `-1` sentinel contract clearly in the event
args type so consumers do not assume the indices are always valid.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Around line 23-29: Resize-handle binding is not idempotent across repeated
init() calls in BitQuickGrid.ts, so surviving .bit-qkg-drg elements keep
accumulating mousedown/touchstart listeners. Update the init()/stop() lifecycle
so each drag handle is bound only once per instance, and ensure the teardown
path removes both the body-level handlers and any per-handle drag listeners
added during init() (for example by tracking bound elements or using a single
delegated handler). Keep the fix centered around the existing bodyClickHandler,
keyDownHandler, and the drag-handle setup in init().

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs`:
- Around line 37-40: The cache update in BitQuickGridPropertyColumn should only
happen after the formatter setup succeeds, because _lastAssignedProperty and
_lastAssignedFormat are being stored before the Format/TProp validation path
completes. Move those assignments out of the early block in the property/format
rebuild logic so they are applied only after the formatter construction and
validation in the column’s render/update path complete successfully, preventing
stale or null _cellTextFunc state on failed validation.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs`:
- Around line 59-77: The sorting path in BitQuickGridItemsProviderRequest is
silently no-oping when SortByColumn is an
IBitQuickGridSortBuilderColumn<TGridItem> but its SortBuilder is null, which can
hide an active sort on BitQuickGridTemplateColumn<TGridItem>. Update
ApplySorting and GetSortByProperties to treat a null SortBuilder as unsupported
by throwing the same NotSupportedException used for non-sort-builder columns,
rather than returning source or empty results.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`:
- Around line 58-61: The _totalItemCountChanged subscription in
BitQuickGridPaginator is initialized with a null callback, so updates from
Value.TotalItemCountChangedSubcribable never trigger a re-render. Update the
BitQuickGridPaginator constructor to wire _totalItemCountChanged to a real
render callback using EventCallback.Factory.Create so that the handler causes
StateHasChanged to run when the total item count changes; keep the fix aligned
with the existing OnParametersSet subscription flow.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor`:
- Around line 5-7: The page metadata description for BitDataGridDemo still uses
the old generic copy, so update the Description on the PageOutlet for the
DataGrid demo to match the richer BitDataGrid positioning used elsewhere in the
component. Keep the change localized to the BitDataGridDemo.razor PageOutlet so
the route metadata and search preview stay consistent with the updated content.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 614-617: Sanitize the openFDA search term before building the
query string in BitQuickGridDemo by removing unsupported characters from
_virtualSampleNameFilter, especially double quotes and backslashes, so the
recalling_firm search does not produce malformed requests. Update the query
construction in the component’s data-loading logic where the "search" dictionary
entry is created, and apply the same sanitization in
BitQuickGridDemo.razor.samples.cs inside example3CsharpCode so the demo code
matches the runtime behavior. Use a cleaned local value derived from
_virtualSampleNameFilter and interpolate that into the recalling_firm query
instead of the raw input.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 305-317: The resize initialization in BitQuickGrid.razor.cs only
re-runs when ComputeColumnsHash() changes, so toggling ResizableColumns on and
off with the same columns leaves the new .bit-qkg-drg handles unbound. Update
the logic around the _lastInitColumnsHash / _jsEventDisposable flow in the
render path so BitQuickGridInit(...) also runs when ResizableColumns changes
state, not just when the column hash differs, and make sure the previous JS
registration is stopped before rebinding.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 4-5: The `@import` statements in extra-components.scss use explicit
.scss extensions, which violates the scss/load-partial-extension rule. Update
the imports to reference the partials without the extension, using the existing
BitQuickGrid and BitQuickGridPaginator paths so the stylesheet continues to
resolve correctly and passes linting.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 4: The QuickGrid demo Razor page still imports the old DTO namespace, so
update the `@using` in BitQuickGridDemo.razor to match the moved DTOs used by the
code-behind: Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo instead of
Demo.Shared.Dtos.QuickGridDemo. Make the namespace import consistent with the
demo DTO types so the page can resolve the QuickGrid demo models correctly.

---

Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 46-48: The public BitDataGridPropertyAccessor.ConvertValue helper
still hides failed coercions by returning the property default, which can be
misused by future edit/filter flows. Update the BitDataGridPropertyAccessor API
so callers use TryConvertValue for failure-aware handling, or make the fallback
behavior explicit by renaming ConvertValue to something like ConvertOrDefault
and keeping it out of the normal public path; ensure SetValue and any other
callers rely on the safer conversion result instead of silent defaults.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 39b39e46-c747-426d-aee8-5fa952e4ffea

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 2bc4693.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor Outdated
@msynk

msynk commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs (1)

58-61: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Total-count subscription still uses a null delegate.

_totalItemCountChanged is initialized with new EventCallback<BitQuickGridPaginationState>(this, null), so the SubscribeOrMove in OnParametersSet registers a no-op. When Value.TotalItemCountChangedSubscribable fires, the paginator won't re-render and the summary/controls stay stale, contradicting the adjacent comment.

Suggested fix
 public BitQuickGridPaginator()
 {
     // The "total item count" handler doesn't need to do anything except cause this component to re-render
-    _totalItemCountChanged = new(new EventCallback<BitQuickGridPaginationState>(this, null));
+    _totalItemCountChanged = new(
+        EventCallback.Factory.Create<BitQuickGridPaginationState>(this, _ => InvokeAsync(StateHasChanged)));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`
around lines 58 - 61, The total-count subscription in BitQuickGridPaginator
still uses a null delegate, so the Value.TotalItemCountChangedSubscribable
callback never triggers a re-render. Update the BitQuickGridPaginator
constructor to initialize _totalItemCountChanged with a real no-op handler that
calls StateHasChanged, and keep OnParametersSet subscribing via SubscribeOrMove
to that callback so the paginator refreshes when total item count changes.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs (1)

10-12: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add a distinct path for DateTimeOffset.

Line 10-12 still force DateTimeOffset through the same DateTime branch used by datetime-local in BitDataGridCellEditor.razor. That editor cannot round-trip offsets, so editable DateTimeOffset cells will be rewritten as local wall time unless every consumer overrides the editor.

Suggested direction
 public enum BitDataGridColumnDataType
 {
     Auto = 0,
     Text,
     Number,
     Boolean,
     Date,
     DateTime,
+    DateTimeOffset,
     Enum
 }

Then map typeof(DateTimeOffset) to the new enum in BitDataGridColumn<TItem>.EffectiveDataType and give BitDataGridCellEditor.razor an offset-preserving editor/parser path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs`
around lines 10 - 12, Add a distinct DateTimeOffset data type path so editable
DateTimeOffset values do not get handled by the existing DateTime branch. Update
BitDataGridColumnDataType and the logic in
BitDataGridColumn<TItem>.EffectiveDataType to map typeof(DateTimeOffset) to the
new enum value, then add an offset-preserving editor/parser branch in
BitDataGridCellEditor.razor instead of reusing the datetime-local path. Keep the
existing DateTime behavior unchanged while ensuring DateTimeOffset round-trips
without losing the offset.
🧹 Nitpick comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss (1)

7-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove .scss extensions from the new imports.

Lines 7–8 violate scss/load-partial-extension. The .scss suffixes should be dropped to match the rule and the pattern used by Sass partial imports.

💡 Suggested fix
-@import "../Components/QuickGrid/BitQuickGrid.scss";
-@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss";
+@import "../Components/QuickGrid/BitQuickGrid";
+@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss` around lines 7
- 8, The new imports in extra-components.scss should use Sass partial paths
without the .scss suffix to satisfy scss/load-partial-extension. Update the two
`@import` statements that reference BitQuickGrid.scss and
BitQuickGridPaginator.scss so they follow the same partial import pattern used
elsewhere in the stylesheet.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs (1)

77-88: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider parsing with CultureInfo.InvariantCulture for deterministic conversion.

Guid.Parse is culture-invariant, but DateOnly.Parse, TimeOnly.Parse, and DateTimeOffset.Parse use the current culture by default. If editor input is serialized in a fixed (e.g. ISO) format, locale-dependent parsing can fail or misinterpret values across environments. Passing CultureInfo.InvariantCulture (matching how values are formatted) makes conversion deterministic.

Confirm the format used when these values are produced for edit, to pick the matching culture/format.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`
around lines 77 - 88, The conversion logic in BitDataGridPropertyAccessor
currently parses DateOnly, TimeOnly, and DateTimeOffset using the current
culture, which can make editor value conversion inconsistent across locales.
Update the parsing path in the property accessor to use a deterministic
culture/format, ideally by confirming how these values are produced for edit and
then applying the matching CultureInfo (for example, invariant culture) when
calling DateOnly.Parse, TimeOnly.Parse, and DateTimeOffset.Parse, while leaving
the Guid branch unchanged.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs (1)

196-199: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused selectionMode field in the selection snippet.

The example 3 razor snippet (Lines 187–195) hard-codes SelectionMode="BitDataGridSelectionMode.Multiple", so the selectionMode field declared here is never referenced by the copy-paste sample. Drop it or wire the snippet's grid to bind it, so pasted code has no dead field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`
around lines 196 - 199, The example3CsharpCode snippet includes an unused
selectionMode field because the corresponding razor sample hard-codes
BitDataGridSelectionMode.Multiple instead of binding to it. Update the sample in
BitDataGridDemo.razor.samples.cs so the snippet either removes selectionMode
entirely or changes the BitDataGrid example to reference the field consistently
with the component markup, keeping the sample code in sync with the demo.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 59-60: The BitDataGrid.razor grid markup currently binds
aria-rowcount to TotalCount even in infinite loading, which can misreport the
number of rows to assistive tech. Update the grid rendering logic in
BitDataGrid’s table/viewport markup so that when OnLoadMore is active it does
not expose the loaded count as the total; instead omit aria-rowcount or use an
unknown value, while keeping the existing TotalCount binding for non-infinite
mode.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 511-518: Remote data paths in BitDataGrid are updating _groups via
ToggleGroupAsync, but OnRead/OnLoadMore only send sorts and filters, so grouping
appears enabled without affecting the rendered remote item list. Update
BitDataGrid to either disable ColumnGroupable for server/infinite modes or
extend the remote request/response flow in OnRead, OnLoadMore, and related
grouping logic to carry and render grouping consistently with _groups.
- Around line 416-421: Materialize the result of ChildrenSelector in
BitDataGrid’s tree-walking logic before checking for children or recursing,
since the current Walk path reuses the same IEnumerable<TItem>? twice and can
re-enumerate lazy/single-pass sequences. Update the tree traversal code around
Walk so it caches the children once per item, then use that cached collection
for both the hasChildren check and the recursive call, including the similar
logic in the other Walk branch mentioned in the review.
- Around line 373-377: Keep paging state consistent when grouping is active in
BitDataGrid’s grouping branch: the current _groups.Count > 0 path sets
_pageItems to _view while _viewGroups is grouped data, so pager methods like
GoToPageAsync and TotalPages still act as if paging is enabled. Update the
BitDataGrid.razor.cs grouping logic to either apply paging to the grouped result
or disable/neutralize paging whenever grouping is active, and make sure the
pager state matches the displayed rows.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor`:
- Around line 24-29: The QuickGrid virtualization markup uses Virtualize inside
a tbody without setting the spacer element, so it may render invalid table
structure. Update the Virtualize component in BitQuickGrid.razor to explicitly
use SpacerElement="tr" alongside the existing ItemsProvider, ItemContent, and
Placeholder settings so the spacer rows match the table markup.

---

Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs`:
- Around line 10-12: Add a distinct DateTimeOffset data type path so editable
DateTimeOffset values do not get handled by the existing DateTime branch. Update
BitDataGridColumnDataType and the logic in
BitDataGridColumn<TItem>.EffectiveDataType to map typeof(DateTimeOffset) to the
new enum value, then add an offset-preserving editor/parser branch in
BitDataGridCellEditor.razor instead of reusing the datetime-local path. Keep the
existing DateTime behavior unchanged while ensuring DateTimeOffset round-trips
without losing the offset.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`:
- Around line 58-61: The total-count subscription in BitQuickGridPaginator still
uses a null delegate, so the Value.TotalItemCountChangedSubscribable callback
never triggers a re-render. Update the BitQuickGridPaginator constructor to
initialize _totalItemCountChanged with a real no-op handler that calls
StateHasChanged, and keep OnParametersSet subscribing via SubscribeOrMove to
that callback so the paginator refreshes when total item count changes.

---

Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 77-88: The conversion logic in BitDataGridPropertyAccessor
currently parses DateOnly, TimeOnly, and DateTimeOffset using the current
culture, which can make editor value conversion inconsistent across locales.
Update the parsing path in the property accessor to use a deterministic
culture/format, ideally by confirming how these values are produced for edit and
then applying the matching CultureInfo (for example, invariant culture) when
calling DateOnly.Parse, TimeOnly.Parse, and DateTimeOffset.Parse, while leaving
the Guid branch unchanged.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 7-8: The new imports in extra-components.scss should use Sass
partial paths without the .scss suffix to satisfy scss/load-partial-extension.
Update the two `@import` statements that reference BitQuickGrid.scss and
BitQuickGridPaginator.scss so they follow the same partial import pattern used
elsewhere in the stylesheet.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 196-199: The example3CsharpCode snippet includes an unused
selectionMode field because the corresponding razor sample hard-codes
BitDataGridSelectionMode.Multiple instead of binding to it. Update the sample in
BitDataGridDemo.razor.samples.cs so the snippet either removes selectionMode
entirely or changes the BitDataGrid example to reference the field consistently
with the component markup, keeping the sample code in sync with the demo.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5d238519-0a8e-4425-80ef-0bcba2a60f5e

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 58170be.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
@msynk

msynk commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs (1)

27-43: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Keep EF misconfiguration detection when the registered executor is unsupported.

Line 43 only returns when the single resolved executor supports the query, but an unsupported registered executor bypasses the EF warning path and falls through to synchronous IQueryable execution. Check all registered executors first, then run the EF-provider detection when none support the query.

Proposed fix
-            var executor = services.GetService<IAsyncQueryExecutor>();
+            var executor = services.GetServices<IAsyncQueryExecutor>()
+                .FirstOrDefault(e => e.IsSupported(queryable));
 
-            if (executor is null)
+            if (executor is not null)
+            {
+                return executor;
+            }
+
+            // It's useful to detect if the developer is unaware that they should be using the EF adapter, otherwise
+            // they will likely never notice and simply deploy an inefficient app that blocks threads on each query.
+            var providerType = queryable.Provider?.GetType();
+            if (providerType is not null && IsEntityFrameworkProviderTypeCache.GetOrAdd(providerType, IsEntityFrameworkProviderType))
             {
-                // It's useful to detect if the developer is unaware that they should be using the EF adapter, otherwise
-                // they will likely never notice and simply deploy an inefficient app that blocks threads on each query.
-                var providerType = queryable.Provider?.GetType();
-                if (providerType is not null && IsEntityFrameworkProviderTypeCache.GetOrAdd(providerType, IsEntityFrameworkProviderType))
-                {
-                    throw new InvalidOperationException(
-                        $"The supplied {nameof(IQueryable)} is provided by Entity Framework. To query it efficiently without blocking threads, " +
-                        $"register an implementation of {nameof(IAsyncQueryExecutor)} in your service collection that wraps EF Core's async query APIs " +
-                        $"(for example ToArrayAsync/CountAsync) and reports IsSupported(queryable) == true for EF queryables. " +
-                        $"Alternatively, supply non-EF data via the Items or ItemsProvider parameters.");
-                }
-            }
-            else if (executor.IsSupported(queryable))
-            {
-                return executor;
+                throw new InvalidOperationException(
+                    $"The supplied {nameof(IQueryable)} is provided by Entity Framework. To query it efficiently without blocking threads, " +
+                    $"register an implementation of {nameof(IAsyncQueryExecutor)} in your service collection that wraps EF Core's async query APIs " +
+                    $"(for example ToArrayAsync/CountAsync) and reports IsSupported(queryable) == true for EF queryables. " +
+                    $"Alternatively, supply non-EF data via the Items or ItemsProvider parameters.");
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs`
around lines 27 - 43, The current AsyncQueryExecutorSupplier logic only checks a
single IAsyncQueryExecutor from GetService, so an unsupported registered
executor can bypass the EF misconfiguration warning and fall back to synchronous
execution. Update the executor lookup to inspect all registered
IAsyncQueryExecutor implementations first and use the first one whose
IsSupported(queryable) returns true, then only run the Entity Framework provider
detection when no executor supports the query. Keep the existing EF warning
behavior in the IAsyncQueryExecutorSupplier flow and preserve the
InvalidOperationException path for unsupported EF queryables.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs (1)

42-50: 🎯 Functional Correctness | 🟠 Major

Allow formatted nullable value types.

Nullable<T> does not implement IFormattable, so the check typeof(IFormattable).IsAssignableFrom(typeof(TProp)) incorrectly rejects valid nullable types like int? or DateTime?. This causes an InvalidOperationException even though the runtime delegate correctly handles unboxing and formatting of the underlying value.

Suggested fix
             if (Format.HasValue())
             {
-                if (typeof(IFormattable).IsAssignableFrom(typeof(TProp)))
+                var formattableType = Nullable.GetUnderlyingType(typeof(TProp)) ?? typeof(TProp);
+                if (typeof(IFormattable).IsAssignableFrom(formattableType))
                 {
                     cellTextFunc = item => ((IFormattable?)compiledPropertyExpression!(item))?.ToString(Format, null);
                 }
                 else
                 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs`
around lines 42 - 50, Update BitQuickGridPropertyColumn’s formatting guard in
the property column setup so Format is allowed for nullable value types like
int? and DateTime?. The current
typeof(IFormattable).IsAssignableFrom(typeof(TProp)) check in the formatting
branch of the column initialization is too strict for Nullable<T>; adjust the
condition to also accept nullable underlying types whose non-nullable value type
implements IFormattable, and keep the existing cellTextFunc formatting path
using the compiledPropertyExpression delegate.
♻️ Duplicate comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs (1)

58-61: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a real render callback for _totalItemCountChanged.

Line 61 still subscribes a null EventCallback, so updates from Value.TotalItemCountChangedSubscribable won't re-render the paginator. That leaves the summary text and navigation state stale after TotalItemCount changes.

Suggested fix
 public BitQuickGridPaginator()
 {
     // The "total item count" handler doesn't need to do anything except cause this component to re-render
-    _totalItemCountChanged = new(new EventCallback<BitQuickGridPaginationState>(this, null));
+    _totalItemCountChanged = new(
+        EventCallback.Factory.Create<BitQuickGridPaginationState>(this, _ => InvokeAsync(StateHasChanged)));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`
around lines 58 - 61, The BitQuickGridPaginator constructor is wiring
_totalItemCountChanged to a null EventCallback, so
TotalItemCountChangedSubscribable updates never trigger a re-render and the
paginator UI can stay stale. Update the BitQuickGridPaginator.razor.cs setup to
use a real render callback tied to the component instance, and ensure the
callback path used by Value.TotalItemCountChangedSubscribable causes the
paginator to refresh when BitQuickGridPaginationState changes.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs (1)

89-98: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the grouping column in Path identity.

Path is the collapse/expand key here, but it only includes level and keyId. Re-grouping level 0 from one column to another with the same key value will reuse the old expansion state for the wrong group.

💡 Minimal fix
-                var path = $"{parentPath}/{level}:{keyId}";
+                var path = $"{parentPath}/{level}:{descriptor.ColumnId}:{keyId}";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`
around lines 89 - 98, The collapse/expand Path in BitDataGridDataProcessor
currently only uses parentPath, level, and keyId, so changing the grouped column
can reuse stale expansion state for a different group. Update the Path
construction to also incorporate the grouping column identity from the current
grouping context, using the existing processing flow in BitDataGridDataProcessor
and the keyId/path generation logic. Keep the identifier culture-invariant and
type-safe, but make sure the group-by column is part of the Path so same-valued
keys from different columns do not collide.
src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss (1)

7-8: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove .scss extensions from @import paths.

Lines 7 and 8 still violate scss/load-partial-extension. The .scss suffixes should be dropped so the imports reference partials correctly.

💡 Suggested fix
-@import "../Components/QuickGrid/BitQuickGrid.scss";
-@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss";
+@import "../Components/QuickGrid/BitQuickGrid";
+@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss` around lines 7
- 8, The import statements in extra-components.scss still include explicit .scss
extensions, which violates the partial import convention. Update the two `@import`
paths to reference the partials without the suffix, keeping the existing
BitQuickGrid and BitQuickGridPaginator targets so the stylesheet loads
correctly.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 209-224: The loading, empty, infinite-end, and group-summary
fallback content in BitDataGrid.razor is being rendered directly inside
row/rowgroup containers without valid gridcell structure. Update the affected
branches in BitDataGrid’s row rendering logic to wrap each message or group
label in a row containing a gridcell that spans all columns, matching the
existing grid row pattern used elsewhere. Apply this consistently to the
Loading/empty branches and the other affected group/infinite-end branches so
screen readers preserve the grid semantics.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 240-244: Tree mode currently leaves paging, filtering, and
grouping controls enabled even though ProcessTreeData does not apply those
operations, so the UI can claim support it does not have. Update BitDataGrid’s
control-gating logic (for example PagingActive, ColumnFilterable, and
ColumnGroupable, plus any related tree-mode checks) to return false or be
suppressed when IsTreeMode is active unless you also implement the operations in
ProcessTreeData. Ensure the pager, filter UI, and grouping UI all stay
consistent with the rendered tree rows.
- Around line 272-278: Remove the stale per-column state when a column
unregisters. In BitDataGrid.RemoveColumn, after removing the column from
_columns and _columnsById, also clear any matching entries from _sorts,
_filters, and _groups using the column.Id so refreshes and remote reads no
longer retain descriptors for a removed column.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs`:
- Around line 96-147: The grid’s column registry becomes stale when mutable
parameters change because BitDataGridColumn.Id is derived from ColumnId/Field
but the column is only registered once via Grid.AddColumn and later removed
using the current Id. Update the BitDataGridColumn lifecycle so changes to
ColumnId or Field re-register the column in BitDataGrid._columnsById, and ensure
removal uses the previously registered key rather than the possibly updated Id;
use the existing BitDataGridColumn, Grid.AddColumn, and Grid.RemoveColumn paths
to keep sort/filter/group lookups aligned.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 61-65: TryConvertValue in BitDataGridPropertyAccessor currently
accepts null as a successful conversion and falls back to DefaultValue(), which
incorrectly turns cleared edits into 0/MinValue for non-nullable targets. Update
the null-handling branch to reject null when the target type is a non-nullable
value type, and only allow null to pass through for nullable/reference targets;
keep the existing conversion flow for other values.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 256-267: The sort state in BitQuickGrid needs to be reconciled
after column recollection, because AddColumn and FinishCollectingColumns can
leave _sortByColumn pointing to a removed column or update header state without
refreshing data. In FinishCollectingColumns(), validate _sortByColumn against
the final _columns collection, clear or replace it when it is no longer present,
and compare the effective sort column/direction before and after recollection.
If the active sort changes, queue a refresh so the grid data query stays in sync
with the header state.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Around line 110-119: Touch-based resize cleanup in BitQuickGrid currently
removes listeners only on touchend, so a canceled gesture can leave handlers
attached. Update the listener setup in the resize handling logic around
handleMouseUp/handleMouseMove to also register touchcancel with the same cleanup
handler, ensuring the same teardown runs when the touch sequence is interrupted.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs`:
- Line 8: The return-type cref in the XML doc for BitQuickGridItemsProvider
should point to the generic result type used by the delegate, not the
non-generic helper type. Update the <returns> documentation on
BitQuickGridItemsProvider so it references
BitQuickGridItemsProviderResult<TGridItem> and matches the ValueTask returned by
the delegate signature.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs`:
- Around line 54-66: Clarify the unsupported-sort path in ApplySorting so it
covers both non-IBitQuickGridSortBuilderColumn<TGridItem> columns and columns
whose SortBuilder is null. Update the XML docs and ColumnNotSortableMessage to
mention the null-SortBuilder case explicitly, and ensure the thrown
NotSupportedException text matches the actual failure path for
BitQuickGridTemplateColumn<TGridItem>.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 556-600: The OData and LoadingTemplate demos are still coupled
through the shared `productsItemsProvider` and `_odataSampleNameFilter`, which
causes one grid to affect the other and leaves the second grid stale. Decouple
the state by giving the OData and LoadingTemplate sections separate
filter/provider fields, or update the `ODataSampleNameFilter` setter to refresh
both `productsDataGrid` and `loadingProductsDataGrid` if sharing is intentional.
Use the `productsItemsProvider`, `loadingProductsDataGrid`, and
`ODataSampleNameFilter` members in `BitQuickGridDemo` to locate the change.

---

Outside diff comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs`:
- Around line 42-50: Update BitQuickGridPropertyColumn’s formatting guard in the
property column setup so Format is allowed for nullable value types like int?
and DateTime?. The current typeof(IFormattable).IsAssignableFrom(typeof(TProp))
check in the formatting branch of the column initialization is too strict for
Nullable<T>; adjust the condition to also accept nullable underlying types whose
non-nullable value type implements IFormattable, and keep the existing
cellTextFunc formatting path using the compiledPropertyExpression delegate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs`:
- Around line 27-43: The current AsyncQueryExecutorSupplier logic only checks a
single IAsyncQueryExecutor from GetService, so an unsupported registered
executor can bypass the EF misconfiguration warning and fall back to synchronous
execution. Update the executor lookup to inspect all registered
IAsyncQueryExecutor implementations first and use the first one whose
IsSupported(queryable) returns true, then only run the Entity Framework provider
detection when no executor supports the query. Keep the existing EF warning
behavior in the IAsyncQueryExecutorSupplier flow and preserve the
InvalidOperationException path for unsupported EF queryables.

---

Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 89-98: The collapse/expand Path in BitDataGridDataProcessor
currently only uses parentPath, level, and keyId, so changing the grouped column
can reuse stale expansion state for a different group. Update the Path
construction to also incorporate the grouping column identity from the current
grouping context, using the existing processing flow in BitDataGridDataProcessor
and the keyId/path generation logic. Keep the identifier culture-invariant and
type-safe, but make sure the group-by column is part of the Path so same-valued
keys from different columns do not collide.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`:
- Around line 58-61: The BitQuickGridPaginator constructor is wiring
_totalItemCountChanged to a null EventCallback, so
TotalItemCountChangedSubscribable updates never trigger a re-render and the
paginator UI can stay stale. Update the BitQuickGridPaginator.razor.cs setup to
use a real render callback tied to the component instance, and ensure the
callback path used by Value.TotalItemCountChangedSubscribable causes the
paginator to refresh when BitQuickGridPaginationState changes.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 7-8: The import statements in extra-components.scss still include
explicit .scss extensions, which violates the partial import convention. Update
the two `@import` paths to reference the partials without the suffix, keeping the
existing BitQuickGrid and BitQuickGridPaginator targets so the stylesheet loads
correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8985d0c7-b2d6-45be-9cc7-d572e4c9af87

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and ad74781.

📒 Files selected for processing (86)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (6)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs

@msynk

msynk commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

♻️ Duplicate comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor (1)

13-14: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The key-default suppression is still out of sync with the keys the grid owns.

Line 36 turns @onkeydown:preventDefault off during edit mode even though Lines 64-72 still consume Enter/Escape, and the non-editing path relies on Grid.PreventCellKeyDefault, which HandleCellKeyDownAsync only updates after the current keydown has already fired. That means browser defaults can still run alongside grid handling on the same keystroke. This needs a per-event solution instead of a render-time boolean.

Also applies to: 32-36, 56-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor`
around lines 13 - 14, The cell key-default handling in BitDataGridCell is using
render-time booleans, so browser defaults can still fire on the same key event
that the grid handles. Update the `@onkeydown`:preventDefault logic to be decided
per event in HandleKeyDown/HandleCellKeyDownAsync, based on the actual key and
current edit state, instead of relying on ShouldPreventKeyDefault or
Grid.PreventCellKeyDefault being updated after the fact. Keep the Enter/Escape
handling in the cell key handlers aligned with the preventDefault decision so
the grid owns those keys consistently.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs (1)

58-61: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Still wire _totalItemCountChanged to a real render callback.

new EventCallback<BitQuickGridPaginationState>(this, null) is a no-op, so updates from Value.TotalItemCountChangedSubscribable never re-render the paginator. The summary text and forward/last-page state stay stale after the grid loads a new total.

Suggested fix
     public BitQuickGridPaginator()
     {
         // The "total item count" handler doesn't need to do anything except cause this component to re-render
-        _totalItemCountChanged = new(new EventCallback<BitQuickGridPaginationState>(this, null));
+        _totalItemCountChanged = new(
+            EventCallback.Factory.Create<BitQuickGridPaginationState>(this, _ => InvokeAsync(StateHasChanged)));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`
around lines 58 - 61, The `_totalItemCountChanged` callback in
`BitQuickGridPaginator` is currently initialized as a no-op, so
`Value.TotalItemCountChangedSubscribable` updates do not trigger a re-render.
Update the constructor wiring in `BitQuickGridPaginator` to use a real render
callback tied to the component (for example, a callback that invokes the
paginator’s render/update path), so the summary text and navigation state
refresh when `TotalItemCount` changes.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss (1)

12-12: 📐 Maintainability & Code Quality | 🟠 Major

Suppress stylelint's ::deep false positives here.

selector-pseudo-element-no-unknown is still firing on every ::deep block, so this stylesheet won't pass lint until the rule is disabled locally or configured to allow Blazor's deep selector.

Also applies to: 62-62, 89-89, 122-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss`
at line 12, Stylelint is flagging Blazor’s `::deep` selector as an unknown
pseudo-element in `BitQuickGridDemo.razor.scss`, so disable or override
`selector-pseudo-element-no-unknown` locally for this stylesheet or configure it
to allow `::deep`. Update all affected `::deep` blocks in this SCSS file so the
demo styles pass lint while keeping the Blazor deep selector behavior intact.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 115-143: The header cell in BitDataGrid is missing accessible sort
state, so assistive tech cannot announce whether a column is sorted. Update the
columnheader element in BitDataGrid.razor to expose the current sort direction
using aria-sort, mapping the existing sort state from sort.Direction to
ascending or descending only when the column is sorted. Use the existing column
header rendering and sort logic around ColumnSortable, sort, and the
role="columnheader" cell to place the attribute correctly.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 252-257: The AddColumn registry update in BitDataGrid must reject
duplicate column IDs before mutating state, since _columnsById[column.Id]
currently overwrites an existing entry while _columns can still contain both
columns. Update AddColumn and the related column registration path around the
other AddColumn usage to check whether the incoming BitDataGridColumn<TItem> has
a ColumnId/Field already present in _columnsById, and skip or throw before
adding it to either collection; keep _columns and _columnsById in sync so
sort/filter/group/footer lookup methods resolve the intended column.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`:
- Around line 12-33: Cleared nullable editor values are being forwarded as empty
strings from BitDataGridCellEditor, so nullable number/date/datetime fields may
not reset to null. Update the value handling in the editor cases for
BitDataGridColumnDataType.Number, Date, DateTime, and DateTimeOffset so that
empty input values are normalized before calling Set or SetDateTimeOffset,
converting "" to null when the bound property is nullable. Apply the same
normalization path in the corresponding accessor logic used by GetNumberString,
GetDate, GetDateTime, and the Set/SetDateTimeOffset handlers so cleared inputs
consistently clear the cell.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs`:
- Around line 145-159: The grid is only being refreshed when the column Id
changes, so changes to Field, Aggregate, Format, or AggregateFormat can leave
the grid using stale accessor/aggregate state. Update
BitDataGridColumn.OnParametersSet (and related registration handling in
BitDataGridColumn/Grid.UpdateColumnRegistration) to detect semantic parameter
changes even when Id stays the same, then trigger the grid to refresh/recompute
its view and aggregates so the active column state stays in sync.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`:
- Around line 4-14: Row reordering in BitDataGridRow currently only works
through drag-and-drop, and the visible drag handle is not keyboard focusable, so
keyboard users have no way to move rows. Update the row/reorder-cell markup in
BitDataGridRow.razor to provide a keyboard-accessible reorder action, such as a
focusable button or equivalent control, and wire it to the same reorder logic
used by Grid.StartRowDrag and Grid.DropRowAsync. Ensure the new control is
reachable when Grid.RowReorderable is enabled and supports a clear keyboard
interaction for moving the row.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 98-99: The fallback conversion in BitDataGridPropertyAccessor is
using culture-sensitive parsing, which can break invariant numeric editor values
like 1.5 in comma-decimal locales. Update the Convert.ChangeType call inside the
property accessor logic to explicitly use CultureInfo.InvariantCulture, keeping
the existing behavior for the surrounding conversion flow while ensuring editor
values are parsed consistently regardless of current culture.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs`:
- Around line 17-23: The mixed-type comparison in
BitDataGridValueComparer.Compare is asymmetric because it tries
Convert.ChangeType based on only one operand, which can return different results
depending on argument order. Update Compare to use a symmetric strategy for
mismatched types, such as comparing x.ToString() and y.ToString() first and only
falling back to type-specific comparison when the string values are equal, so
the comparer remains stable and consistent for IComparer<T>.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs`:
- Around line 493-501: The keyboard-navigation sample snippet is out of sync
with the live editable grid example because it omits the row-save callback used
by the demo. Update example17RazorCode in BitDataGridDemo.razor.samples.cs to
include the same OnRowSave handler alongside Editable="true", keeping the
snippet consistent with the live BitDataGrid example and the associated Product
grid behavior.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 101: The BitQuickGrid ItemKey binding is using plain string syntax
instead of a Razor lambda expression, which causes the delegate to be passed
incorrectly. Update the ItemKey usage in BitQuickGridDemo.razor at both
occurrences to use Razor expression syntax with @(...), matching the
Func<TGridItem, object> signature on BitQuickGrid so the ProductDto key selector
is bound as a lambda rather than a string literal.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 357-359: The `BitQuickGridPaginationState` entry has the wrong
description and currently describes a UI component instead of the state model.
Update the documentation text in the `BitQuickGridDemo` data entry for
`BitQuickGridPaginationState` so it matches the pagination state API and stays
consistent with the neighboring `BitQuickGridPaginator` entry.
- Around line 618-624: Avoid sending an empty openFDA search clause from the
QuickGrid data loader: in BitQuickGridDemo.razor.cs, the logic that builds the
query dictionary for the openFDA request should skip adding the search entry
when _virtualSampleNameFilter is empty or whitespace. Update the query-building
path around the firmFilter variable so the initial load omits search entirely
and only adds recalling_firm:"..." when a real filter value exists.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs`:
- Around line 409-417: The openFDA query in the QuickGrid demo currently always
adds the search parameter from BitQuickGridDemo.razor.samples.cs, which produces
an empty Lucene filter when _virtualSampleNameFilter is blank and hides the
default results. Update the query construction around firmFilter and the query
dictionary so that skip and limit are always included, but search is only added
when firmFilter is not empty; keep the existing sanitization logic for
quotes/backslashes and use the same query-building path in the sample method.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor`:
- Around line 13-14: The cell key-default handling in BitDataGridCell is using
render-time booleans, so browser defaults can still fire on the same key event
that the grid handles. Update the `@onkeydown`:preventDefault logic to be decided
per event in HandleKeyDown/HandleCellKeyDownAsync, based on the actual key and
current edit state, instead of relying on ShouldPreventKeyDefault or
Grid.PreventCellKeyDefault being updated after the fact. Keep the Enter/Escape
handling in the cell key handlers aligned with the preventDefault decision so
the grid owns those keys consistently.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`:
- Around line 58-61: The `_totalItemCountChanged` callback in
`BitQuickGridPaginator` is currently initialized as a no-op, so
`Value.TotalItemCountChangedSubscribable` updates do not trigger a re-render.
Update the constructor wiring in `BitQuickGridPaginator` to use a real render
callback tied to the component (for example, a callback that invokes the
paginator’s render/update path), so the summary text and navigation state
refresh when `TotalItemCount` changes.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss`:
- Line 12: Stylelint is flagging Blazor’s `::deep` selector as an unknown
pseudo-element in `BitQuickGridDemo.razor.scss`, so disable or override
`selector-pseudo-element-no-unknown` locally for this stylesheet or configure it
to allow `::deep`. Update all affected `::deep` blocks in this SCSS file so the
demo styles pass lint while keeping the Blazor deep selector behavior intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 00b77e6a-f588-4198-83f8-df9488c74cc0

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 1803009.

📒 Files selected for processing (87)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (7)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs

@msynk

msynk commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss (1)

7-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove .scss extensions from the new imports.

Lines 7 and 8 violate the scss/load-partial-extension stylelint rule. Remove the extensions to match the convention used throughout the file.

-@import "../Components/QuickGrid/BitQuickGrid.scss";
-@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss";
+@import "../Components/QuickGrid/BitQuickGrid";
+@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss` around lines 7
- 8, The new imports in extra-components.scss are using explicit .scss
extensions, which violates the scss/load-partial-extension convention. Update
the import statements for BitQuickGrid and BitQuickGridPaginator to use
partial-style imports without the extension, matching the rest of the file and
the standard Sass import pattern.

Source: Linters/SAST tools

src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs (1)

58-62: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wire the total-count subscription to a real render callback.

_totalItemCountChanged is still initialized with a null delegate, so the subscription created in OnParametersSet is a no-op. When Value.TotalItemCountChangedSubscribable fires, the paginator won't re-render and pagination controls/summary stay stale — contradicting the adjacent comment.

Suggested fix
 public BitQuickGridPaginator()
 {
     // The "total item count" handler doesn't need to do anything except cause this component to re-render
-    _totalItemCountChanged = new(new EventCallback<BitQuickGridPaginationState>(this, null));
+    _totalItemCountChanged = new(
+        EventCallback.Factory.Create<BitQuickGridPaginationState>(this, _ => InvokeAsync(StateHasChanged)));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`
around lines 58 - 62, The total-count subscription in BitQuickGridPaginator is
currently backed by a null EventCallback, so the handler created in
OnParametersSet never triggers a re-render. Update the constructor
initialization of _totalItemCountChanged to use a real callback on this
component that causes a refresh (for example via StateHasChanged or an
equivalent render-invoking method) so Value.TotalItemCountChangedSubscribable
actually updates the paginator UI.
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (1)

1111-1132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Row identity uses reference equality instead of KeyField.

MoveRowAsync and DropRowAsync locate rows via EqualityComparer<TItem>.Default, while selection/edit/tree paths consistently use KeyEquals/GetKey. In the common client-mode path the same instances are used so this works, but it diverges from the key-based identity model and would mismatch if a parent supplies KeyField with re-materialized instances. Consider routing these lookups through KeyEquals for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 1111 - 1132, Row lookup in BitDataGrid’s MoveRowAsync and
DropRowAsync is using EqualityComparer<TItem>.Default instead of the grid’s
KeyField-based identity model. Update the row matching logic to use the same
key-based helpers used elsewhere in the component, such as KeyEquals and GetKey,
so re-materialized items still resolve correctly when KeyField is set. Make the
lookup consistent in both MoveRowAsync and DropRowAsync without changing the
public behavior of row reordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 172-199: The filter row and other row sections in
BitDataGrid.razor are rendering child divs inside role="row" without explicit
cell roles, which breaks grid semantics for assistive tech. Update the row cell
markup in the filter row, infinite placeholder, and footer rendering paths so
each child inside the row uses the correct role="columnheader" or
role="gridcell" as appropriate, while keeping the existing row structure and
using the existing BitDataGrid rendering blocks and column helpers to locate the
affected markup.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 1205-1211: The keyboard focus logic in CellTabIndex is leaving the
grid without any tabbable cell when _focusedRow is no longer present in
NavigableRows after paging, filtering, sorting, or refresh. Update the
CellTabIndex method in BitDataGrid so it first checks whether the focused row
still exists in the current rows; if not, fall back to the first navigable cell
instead of returning -1 for every cell. Keep the existing IsCellFocused behavior
when the focused row is still visible, and make sure the fallback uses the same
first-cell logic already used when no focused row is set.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 250-253: RefreshDataAsync currently waits for RefreshDataCoreAsync
to finish before calling StateHasChanged, so the loading state from
_pendingDataLoadCancellationTokenSource may never render during public
refreshes. Update BitQuickGrid.razor.cs so RefreshDataAsync triggers a render
immediately after entering the loading state inside RefreshDataCoreAsync, then
continues the async load and renders again when complete. Use the
RefreshDataAsync and RefreshDataCoreAsync methods to locate the change and
ensure IsLoading/LoadingTemplate becomes visible as soon as refresh starts.
- Around line 411-430: Use the Virtualize parameter in BitQuickGrid’s
refresh/load path instead of relying on the _virtualizeComponent reference,
since that field can be null before capture or stale after toggling
virtualization off. Update the branching in the refresh logic so the virtualized
path is chosen whenever Virtualize is true, and make sure the non-virtualized
provider request is never used for a virtualized grid.

---

Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs`:
- Around line 58-62: The total-count subscription in BitQuickGridPaginator is
currently backed by a null EventCallback, so the handler created in
OnParametersSet never triggers a re-render. Update the constructor
initialization of _totalItemCountChanged to use a real callback on this
component that causes a refresh (for example via StateHasChanged or an
equivalent render-invoking method) so Value.TotalItemCountChangedSubscribable
actually updates the paginator UI.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Around line 7-8: The new imports in extra-components.scss are using explicit
.scss extensions, which violates the scss/load-partial-extension convention.
Update the import statements for BitQuickGrid and BitQuickGridPaginator to use
partial-style imports without the extension, matching the rest of the file and
the standard Sass import pattern.

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 1111-1132: Row lookup in BitDataGrid’s MoveRowAsync and
DropRowAsync is using EqualityComparer<TItem>.Default instead of the grid’s
KeyField-based identity model. Update the row matching logic to use the same
key-based helpers used elsewhere in the component, such as KeyEquals and GetKey,
so re-materialized items still resolve correctly when KeyField is set. Make the
lookup consistent in both MoveRowAsync and DropRowAsync without changing the
public behavior of row reordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec9f6320-afb3-4ea2-9c62-75b2567a8dfb

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and c3a5f87.

📒 Files selected for processing (87)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (7)
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs Outdated
@msynk

msynk commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (1)

232-232: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

VisibleColumns allocates a fresh list on every access in hot paths.

VisibleColumns materializes a new List<> each call. It's read repeatedly per render and, more notably, inside SnapToRenderedColumn's while loop via ResolveColSpan (Line 1351 → Line 1321), producing O(n) list allocations per keystroke during cell navigation. AllPageSelected/SomePageSelected (Lines 912, 920) similarly rebuild lists per render. Consider caching the visible-column snapshot (invalidated on column add/remove/visibility change) and having ResolveColSpan accept the already-resolved cols to avoid re-querying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs` at
line 232, VisibleColumns is rebuilding a new list on every access, which is
expensive in render and navigation hot paths. Update BitDataGrid so the
visible-column snapshot is cached and invalidated when columns are added,
removed, or visibility changes, and then reuse that snapshot in all callers. In
particular, adjust ResolveColSpan and the SnapToRenderedColumn flow to accept
and use the already-resolved columns instead of re-reading VisibleColumns, and
update AllPageSelected/SomePageSelected to avoid repeated list materialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Line 232: VisibleColumns is rebuilding a new list on every access, which is
expensive in render and navigation hot paths. Update BitDataGrid so the
visible-column snapshot is cached and invalidated when columns are added,
removed, or visibility changes, and then reuse that snapshot in all callers. In
particular, adjust ResolveColSpan and the SnapToRenderedColumn flow to accept
and use the already-resolved columns instead of re-reading VisibleColumns, and
update AllPageSelected/SomePageSelected to avoid repeated list materialization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cf6ebab1-e8c6-451a-b6ed-309aedf570ee

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and f1e27e0.

📒 Files selected for processing (87)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (7)
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs

@msynk

msynk commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

♻️ Duplicate comments (4)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor (1)

101-101: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Bind ItemKey as a Razor lambda, not a string.

Line 101 and Line 116 still pass "(p => p.Id)" as text, so ItemKey won't bind to the delegate parameter.

Suggested fix
-                <BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Virtualize>
+                <BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="@(p => p.Id)" TGridItem="ProductDto" Virtualize>
...
-                <BitQuickGrid `@ref`="loadingProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">
+                <BitQuickGrid `@ref`="loadingProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="@(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">

Also applies to: 116-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`
at line 101, The BitQuickGridDemo.razor usages of BitQuickGrid are passing
ItemKey as a quoted string instead of a Razor lambda, so the delegate never
binds correctly. Update the ItemKey attribute in the BitQuickGrid component
instances referenced by productsDataGrid and the second grid usage to use an
unquoted lambda expression targeting p => p.Id. Keep the fix consistent wherever
BitQuickGrid is configured with ItemKey so the parameter is treated as an
expression, not text.
src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs (1)

258-269: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Queue a refresh when recollection introduces a new default sort.

AddColumn can set _sortByColumn from isDefaultSortDirection during later column recollection, but FinishCollectingColumns only queues refreshes when the active sort column was removed. The header can show the new default sort while the data remains queried with the previous no-sort state.

Suggested direction
+    private BitQuickGridColumnBase<TGridItem>? _sortByColumnBeforeCollection;
+    private bool _sortByAscendingBeforeCollection;
+
     private void StartCollectingColumns()
     {
+        _sortByColumnBeforeCollection = _sortByColumn;
+        _sortByAscendingBeforeCollection = _sortByAscending;
         _columns.Clear();
         _collectingColumns = true;
     }
@@
         if (_sortByColumn is not null && _columns.Contains(_sortByColumn) is false)
         {
             _sortByColumn = null;
             _sortByAscending = false;
             _queueSortReconciliationRefresh = true;
         }
+        else if (!ReferenceEquals(_sortByColumn, _sortByColumnBeforeCollection)
+            || _sortByAscending != _sortByAscendingBeforeCollection)
+        {
+            _queueSortReconciliationRefresh = true;
+        }

Also applies to: 382-395

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`
around lines 258 - 269, Add a refresh when column recollection assigns a new
default sort in AddColumn, because FinishCollectingColumns only handles the case
where the active sort column disappears. Update the BitQuickGrid.AddColumn /
FinishCollectingColumns flow so that when _sortByColumn is set from
isDefaultSortDirection during recollection, the grid also queues a refresh to
re-query data with the new sort state, keeping the header and data in sync.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs (2)

333-335: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject re-key collisions before overwriting the column registry.

UpdateColumnRegistration can still overwrite an existing column when a column changes to an ID already registered by another column, reintroducing _columns/_columnsById desync for sort/filter/group lookups.

Proposed fix
         if (_columnsById.TryGetValue(oldId, out var existing) && ReferenceEquals(existing, column))
             _columnsById.Remove(oldId);
+        if (_columnsById.TryGetValue(column.Id, out var collision) && !ReferenceEquals(collision, column))
+            throw new InvalidOperationException($"Duplicate {nameof(BitDataGridColumn<TItem>)} id '{column.Id}'. Set a unique ColumnId.");
         _columnsById[column.Id] = column;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 333 - 335, UpdateColumnRegistration in BitDataGrid to reject ID
collisions before re-keying the registry. Before assigning column.Id into
_columnsById, check whether the new ID is already mapped to a different column
and avoid overwriting that entry; only remove oldId when it still points to the
same column. Keep _columns, _columnsById, and the sort/filter/group lookup paths
consistent by handling the collision in UpdateColumnRegistration rather than
letting the later assignment replace another column.

311-319: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Refresh after removing active column descriptors.

RemoveColumn drops matching sort/filter/group descriptors but only re-renders. If the removed column was filtering, sorting, or grouping the current view, _view, _pageItems, and remote request descriptors stay stale until another action refreshes.

Proposed fix
-            _sorts.RemoveAll(s => s.ColumnId == key);
-            _filters.RemoveAll(f => f.ColumnId == key);
-            _groups.RemoveAll(g => g.ColumnId == key);
-            InvokeAsync(StateHasChanged);
+            var stateChanged =
+                _sorts.RemoveAll(s => s.ColumnId == key) > 0 |
+                _filters.RemoveAll(f => f.ColumnId == key) > 0 |
+                _groups.RemoveAll(g => g.ColumnId == key) > 0;
+
+            _ = InvokeAsync(stateChanged ? RefreshAsync : StateHasChanged);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 311 - 319, RemoveColumn in BitDataGrid should do more than call
StateHasChanged after deleting a column and its matching sort/filter/group
descriptors. After updating _columns, _columnsById, _sorts, _filters, and
_groups, trigger the same data refresh path used by the grid when descriptors
change so _view, _pageItems, and any remote request descriptors are rebuilt
immediately. Use the existing refresh/update logic in BitDataGrid rather than
only re-rendering, and keep the behavior scoped to the RemoveColumn flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 147-152: The icon-only grouping controls in BitDataGrid.razor need
explicit accessible names and state instead of relying on symbols and CSS
classes. Update the grouping button markup in the ColumnGroupable/IsGrouped
area, and the related grouped-row toggle buttons around the other referenced
section, so each control exposes a clear label plus its current
expanded/collapsed or grouped state through accessible attributes. Keep the
existing ToggleGroupAsync and grouped UI behavior, but add the necessary
accessible text/state metadata directly on those buttons so assistive tech can
identify and announce them correctly.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 240-245: Paging is still being treated as active during infinite
scrolling, which makes pager and count state follow the loaded batch instead of
the full remote stream. Update the PagingActive property in BitDataGrid to also
return false when infinite scrolling is enabled, alongside the existing grouping
and tree-mode checks, so it stays aligned with OnLoadMore and the “no paging UI”
behavior.
- Around line 1526-1535: The `ColumnWidthToken` helper in `BitDataGrid.razor.cs`
formats CSS length values using the current culture for `column.MinWidth` and
`column.MaxWidth`, which can produce invalid CSS in locales that use comma
decimals. Update the interpolations in
`ColumnWidthToken(BitDataGridColumn<TItem> column)` to format all numeric length
values with `CultureInfo.InvariantCulture`, matching the existing `ResizedWidth`
handling, so the generated `minmax(...)`, `px`, and related CSS tokens are
always culture-safe.
- Around line 178-183: The cell keydown suppression state is stale because
BitDataGridCell reads BitDataGrid.PreventCellKeyDefault before
HandleCellKeyDownAsync updates _preventCellKeyDefault, so a handled navigation
key can incorrectly suppress the next Tab or printable key. Update BitDataGrid
and BitDataGridCell so suppression is determined per key before the event is
processed, or remove the shared toggle entirely; use the existing
HandleCellKeyDownAsync and PreventCellKeyDefault symbols to keep the logic
aligned with the actual key being handled.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss`:
- Around line 264-271: The nested group styling in BitDataGrid only applies to
the first level because the current selector in the group-row/group-cell rules
matches only `--bit-dtg-group-level:0`. Update the styling in the
`bit-dtg-group-row` / `bit-dtg-group-cell` rules to use the CSS variable value
directly (instead of a hardcoded level-0 substring match) so all nested group
levels receive the hierarchy cue and deeper levels no longer keep the
transparent border.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts`:
- Around line 6-18: The initInfiniteScroll check flow can fire
OnInfiniteScrollNearEndAsync multiple times before the prior call finishes,
causing overlapping loads and duplicate interop. Update the initInfiniteScroll
logic by adding an in-flight state around the
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync') call inside check(),
skip new invocations while one is pending, and trigger another check only after
the current promise settles.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor`:
- Around line 134-141: In SetDateTimeOffset, the fallback offset currently comes
from DateTimeOffset.Now.Offset when Value is null, which can mismatch the
entered timestamp; derive the offset from the parsed local DateTime instead.
Update the DateTimeOffset construction path in BitDataGridCellEditor.razor so
the offset is taken from the edited local timestamp (using the local time zone’s
offset for that DateTime) while preserving the existing current.Value.Offset
behavior when Value already has a DateTimeOffset.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`:
- Around line 13-15: Update the drag handle in BitDataGridRow.razor so its
accessible name/description includes the ArrowUp/ArrowDown keyboard shortcut,
since the current aria-label on the button only says “Reorder row” and the title
text is not reliable for assistive tech. Adjust the button markup in the row
drag handle to expose the shortcut in accessible text, and keep
HandleReorderKeyDown as the keyboard interaction entry point.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 117-119: The grouping sort logic in BitDataGridDataProcessor
currently treats every non-Descending direction as ascending, so
BitDataGridSortDirection.None still reorders groups. Update the grouping branch
that assigns grouped in the processor to handle None explicitly, either by
preserving the original group order or by skipping sorting for that descriptor,
and keep Descending and Ascending behavior distinct.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs`:
- Around line 3-6: The public type rename from BitDataGridPaginationState to
BitQuickGridPaginationState is breaking downstream consumers, so keep a
backward-compatible obsolete shim for the old name. Add an obsolete
BitDataGridPaginationState type that forwards to or aliases the new
BitQuickGridPaginationState, and ensure the new BitQuickGridPaginationState
remains the primary implementation so existing code still compiles while
migration is guided.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor`:
- Around line 74-77: The selection mode toggle in BitDataGridDemo currently only
updates selectionMode, so switching from Multiple to Single can leave
selectedProducts containing multiple items. Update the Single button handler
(and any related selection-mode switch logic in the demo component) to
immediately trim selectedProducts down to a single item or clear extras when
BitDataGridSelectionMode.Single is chosen, using the existing selectionMode and
selectedProducts state in BitDataGridDemo.razor.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs`:
- Around line 153-164: The server-side filtering in LoadServerData currently
ignores the filter operator and only applies substring matching based on
ColumnId and Value. Update the filter handling in the LoadServerData loop to
inspect each filter’s operator and map it to the correct predicate for
Product.Name, Product.Category, Product.Supplier, Product.Price, and
Product.Stock, including equals, range, and null-check cases instead of always
using Contains. Keep the fix localized to the existing request.Filters iteration
and switch logic.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs`:
- Around line 183-185: Update the filter metadata in
BitDataGridDemo.razor.params.cs so the documented default matches the actual
BitDataGridFilterOperator contract. The issue is that the Operator entry still
advertises Contains as the default even though BitDataGridFilterOperator now
starts with Unspecified = 0; change the DefaultValue on that Operator definition
to reflect Unspecified and keep the LinkType/Href reference intact. Also verify
the surrounding filter parameter metadata entries remain consistent with the DTO
shape used by the demo docs.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs`:
- Around line 99-125: Update the parameter documentation strings in
BitQuickGridDemo so the remaining “data grid” wording is renamed to
“BitQuickGrid” for the RowClass, RowClassSelector, RowStyle, and
RowStyleSelector entries. Keep the existing structure in the docs list and only
change the Description text in the BitQuickGridDemo.razor.cs component metadata
so it matches the component being documented.
- Around line 621-625: The openFDA query builder in BitQuickGridDemo.razor.cs is
passing req.Count through as-is, so requests without a count can end up with
API-defined page sizes and break paging/virtualization. Update the query
construction in the grid data provider to use an explicit limit fallback when
req.Count is null, keeping the existing skip/start index behavior intact and
preserving consistent window sizing for the QuickGrid request path.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 333-335: UpdateColumnRegistration in BitDataGrid to reject ID
collisions before re-keying the registry. Before assigning column.Id into
_columnsById, check whether the new ID is already mapped to a different column
and avoid overwriting that entry; only remove oldId when it still points to the
same column. Keep _columns, _columnsById, and the sort/filter/group lookup paths
consistent by handling the collision in UpdateColumnRegistration rather than
letting the later assignment replace another column.
- Around line 311-319: RemoveColumn in BitDataGrid should do more than call
StateHasChanged after deleting a column and its matching sort/filter/group
descriptors. After updating _columns, _columnsById, _sorts, _filters, and
_groups, trigger the same data refresh path used by the grid when descriptors
change so _view, _pageItems, and any remote request descriptors are rebuilt
immediately. Use the existing refresh/update logic in BitDataGrid rather than
only re-rendering, and keep the behavior scoped to the RemoveColumn flow.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs`:
- Around line 258-269: Add a refresh when column recollection assigns a new
default sort in AddColumn, because FinishCollectingColumns only handles the case
where the active sort column disappears. Update the BitQuickGrid.AddColumn /
FinishCollectingColumns flow so that when _sortByColumn is set from
isDefaultSortDirection during recollection, the grid also queues a refresh to
re-query data with the new sort state, keeping the header and data in sync.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 101: The BitQuickGridDemo.razor usages of BitQuickGrid are passing
ItemKey as a quoted string instead of a Razor lambda, so the delegate never
binds correctly. Update the ItemKey attribute in the BitQuickGrid component
instances referenced by productsDataGrid and the second grid usage to use an
unquoted lambda expression targeting p => p.Id. Keep the fix consistent wherever
BitQuickGrid is configured with ItemKey so the parameter is treated as an
expression, not text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 40fd53cb-99b5-4d7e-92dc-e64c25c11318

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 512597d.

📒 Files selected for processing (87)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (7)
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs

Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss Outdated
@msynk

msynk commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@msynk Sure — I’ll run another full review of the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs (1)

17-30: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mixed-type tie-break is still asymmetric.

Line 21 makes "true" and true equal via case-insensitive text, but Line 24-Line 27 still lets only the left operand break that tie. Compare(true, "true") returns 0, while Compare("true", true) can return nonzero via string.CompareTo("True"). That still violates IComparer<object?> and can destabilize sorting.

Suggested fix
-        if (x is IComparable cx2)
-        {
-            try { return cx2.CompareTo(Convert.ChangeType(y, x.GetType(), System.Globalization.CultureInfo.InvariantCulture)); }
-            catch { /* fall through */ }
-        }
-
-        return 0;
+        return string.Compare(x.GetType().FullName, y.GetType().FullName, StringComparison.Ordinal);
What ordering guarantees must .NET IComparer<T>.Compare satisfy, and is string.CompareTo case-sensitive?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs`
around lines 17 - 30, The mixed-type fallback in
BitDataGridValueComparer.Compare is still asymmetric because the tie-break only
runs from the left operand’s type, which can return different results when x and
y are swapped. Update the comparison logic so any type-specific tie-break is
applied symmetrically for both operands, or remove the asymmetric
Convert.ChangeType-based fallback entirely and rely on a single deterministic
ordering path after the string comparison tie. Use the Compare method in
BitDataGridValueComparer as the location to make the ordering contract
consistent for IComparer<object?>.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor (1)

101-101: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Bind ItemKey as a Razor lambda.

Line 101 and Line 116 still pass the selector as a quoted attribute value. If BitQuickGrid.ItemKey is the delegate parameter declared on the component, this will not bind as Func<TGridItem, object>; use @(...) in both places.

Suggested fix
-                <BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Virtualize>
+                <BitQuickGrid `@ref`="productsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="@(p => p.Id)" TGridItem="ProductDto" Virtualize>
@@
-                <BitQuickGrid `@ref`="loadingProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">
+                <BitQuickGrid `@ref`="loadingProductsDataGrid" ItemsProvider="`@productsItemsProvider`" ItemKey="@(p => p.Id)" TGridItem="ProductDto" Pagination="pagination3">
#!/bin/bash
set -euo pipefail

ast-grep outline src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs --match ItemKey --view expanded || true

echo
sed -n '1,220p' src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs | nl -ba | rg -n -C2 '\bItemKey\b|\[Parameter\]'

echo
sed -n '98,118p' src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor | nl -ba

Also applies to: 116-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`
at line 101, The QuickGrid demo is passing ItemKey as a quoted string instead of
a Razor lambda, so it won’t bind to the delegate-typed parameter on
BitQuickGrid. Update the BitQuickGrid usages in BitQuickGridDemo.razor to wrap
the selector in Razor expression syntax with @(...) so both ItemKey bindings are
passed as Func<TGridItem, object>. Use the BitQuickGrid component and its
ItemKey parameter as the main anchors when editing the two affected instances.
src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss (1)

264-268: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a logical border for the group marker.

This hard-codes the hierarchy cue to the left side, so grouped rows render incorrectly in RTL mode even though the grid supports RTL elsewhere.

Proposed fix
 .bit-dtg-group-row .bit-dtg-group-cell {
-    border-left: 3px solid var(--bit-clr-pri);
+    border-inline-start: 3px solid var(--bit-clr-pri);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss` around
lines 264 - 268, The group marker styling in BitDataGrid.scss is hard-coded to
the left side, which breaks RTL rendering. Update the .bit-dtg-group-row
.bit-dtg-group-cell rule to use a logical border/edge property instead of a
physical left border so the hierarchy cue follows text direction, and keep the
existing visual treatment tied to the group row/cell selector.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`:
- Around line 442-446: The pager buttons in BitDataGrid’s paging controls use
symbol-only labels, which are not accessible names for screen readers. Update
the button elements in the pager markup so each one has an explicit aria-label
describing its action: first page, previous page, next page, and last page. Keep
the existing click handlers and disabled conditions intact while adding the
accessible labels to the same button symbols.
- Around line 124-128: The sortable header activator in BitDataGrid.razor is
still rendered as a focusable span, which forces custom keyboard handling and
can let Space scroll the page instead of acting like a button. Update the header
markup used by the sortable column UI (including the repeated header template
around OnHeaderClick/OnHeaderKeyDown) to render a real button with type="button"
whenever ColumnSortable(column) is true, and remove the custom keydown handling
since native button behavior will cover Enter and Space.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`:
- Around line 418-426: Reset the tree initialization state when the data source
changes so `ProcessTreeData()` can reapply `TreeInitiallyExpanded` on a new
hierarchy. In `BitDataGrid`, update the refresh path that checks
`inputsChanged`/`_dataInitialized` to also clear `_treeInitialized` whenever a
new `Items` source is detected (or otherwise when the root tree data changes)
before calling `RefreshAsync()`. This ensures the tree bootstrap logic in
`ProcessTreeData()` runs again for the new items instead of keeping the old
collapsed state.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts`:
- Around line 18-29: The infinite-scroll retry logic in BitDataGrid.ts keeps
calling check() after OnInfiniteScrollNearEndAsync settles, which can loop
forever when no new rows are added. Update the pending/finally flow around
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync') so check() only runs
again if the last load actually extended the viewport or the .NET callback
reports that more data was added/available. Use the existing disposed/pending
guard in this callback path to prevent re-issuing loads when end-of-data is
reached.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`:
- Around line 4-8: Row dragging in BitDataGridRow conflicts with inline editing
because the entire row is marked draggable while editable controls are rendered
inside it. Update the BitDataGridRow.razor row container and the editable
content path so drag behavior is either limited to a dedicated handle or
disabled whenever Editing is true. Make sure the drag handlers tied to
Grid.StartRowDrag and Grid.DropRowAsync only activate for reorder interactions
and do not interfere with editor input/selection inside the row.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`:
- Around line 198-265: In BitDataGridDataProcessor.Matches, blank filter values
should be treated the same as omitted filters instead of being evaluated as real
values. Add an early check after the Unspecified/empty handling so that empty or
whitespace-only filter.Value is ignored and returns true, and make sure this
applies before the comparison and string-operator branches so operators like
DoesNotContain and numeric comparisons don’t run against "".

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`:
- Around line 37-45: The inline edit path in
BitDataGridPropertyAccessor<TItem>.SetValue is mutating only a copied TItem, so
struct rows never persist changes back to the grid state. Fix the edit flow
around SetEditValue and CommitEditAsync so value-type rows are either disallowed
for this editing path or updated by reference/index in the stored row slot
instead of passing the item by value. Use
BitDataGridPropertyAccessor<TItem>.SetValue and the edit-item storage logic to
ensure the committed row reflects the edited values.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts`:
- Around line 26-40: The stop() cleanup in BitQuickGrid.ts only removes the
idle/body and per-handle bindings, but any document-level listeners added by
handleMouseDown during an active drag can remain attached. Update the stop()
teardown to also unregister the move/up/cancel listeners that handleMouseDown
installs, and clear any active drag state so re-init or disposal cannot keep
mutating a stale th. Use the existing stop() and handleMouseDown symbols to
locate the cleanup and mirror the same listener registration paths when removing
them.

---

Duplicate comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss`:
- Around line 264-268: The group marker styling in BitDataGrid.scss is
hard-coded to the left side, which breaks RTL rendering. Update the
.bit-dtg-group-row .bit-dtg-group-cell rule to use a logical border/edge
property instead of a physical left border so the hierarchy cue follows text
direction, and keep the existing visual treatment tied to the group row/cell
selector.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs`:
- Around line 17-30: The mixed-type fallback in BitDataGridValueComparer.Compare
is still asymmetric because the tie-break only runs from the left operand’s
type, which can return different results when x and y are swapped. Update the
comparison logic so any type-specific tie-break is applied symmetrically for
both operands, or remove the asymmetric Convert.ChangeType-based fallback
entirely and rely on a single deterministic ordering path after the string
comparison tie. Use the Compare method in BitDataGridValueComparer as the
location to make the ordering contract consistent for IComparer<object?>.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor`:
- Line 101: The QuickGrid demo is passing ItemKey as a quoted string instead of
a Razor lambda, so it won’t bind to the delegate-typed parameter on
BitQuickGrid. Update the BitQuickGrid usages in BitQuickGridDemo.razor to wrap
the selector in Razor expression syntax with @(...) so both ItemKey bindings are
passed as Func<TGridItem, object>. Use the BitQuickGrid component and its
ItemKey parameter as the main anchors when editing the two affected instances.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8996c2c5-b70e-4f3e-adae-96df899bf642

📥 Commits

Reviewing files that changed from the base of the PR and between d984493 and 469c04a.

📒 Files selected for processing (87)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCell.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridCellEditor.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridGroup.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridValueComparer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridAggregateType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridCellEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridColumnDataType.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridFilterOperator.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridGroupDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridPagerPosition.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridReadResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridRowReorderEventArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSelectionMode.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDescriptor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Models/BitDataGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/AppJsonContext.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecall.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/FoodRecallQueryResult.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Meta.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Openfda.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/QuickGridDemo/Results.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.params.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/BitDataGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/FileSystemData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/Product.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SampleData.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/DataGrid/SupplierModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
💤 Files with no reviewable changes (7)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridColumnBase.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/BitDirection.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
  • src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Dtos/DataGridDemo/Openfda.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/IBitDataGridSortBuilderColumn.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/ItemsProvider/BitDataGridItemsProvider.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Columns/BitDataGridPropertyColumn.cs

Comment on lines +124 to +128
<span class="bit-dtg-htext"
role="@(ColumnSortable(column) ? "button" : null)"
tabindex="@(ColumnSortable(column) ? 0 : -1)"
@onclick="e => OnHeaderClick(column, e)"
@onkeydown="e => OnHeaderKeyDown(column, e)">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a real button for sortable headers.

The focusable <span> still needs custom keyboard handling, and Space on it can scroll the viewport/page instead of behaving like a native button. Rendering the header activator as <button type="button"> would give you correct Enter/Space behavior and let you drop OnHeaderKeyDown.

Also applies to: 417-424

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`
around lines 124 - 128, The sortable header activator in BitDataGrid.razor is
still rendered as a focusable span, which forces custom keyboard handling and
can let Space scroll the page instead of acting like a button. Update the header
markup used by the sortable column UI (including the repeated header template
around OnHeaderClick/OnHeaderKeyDown) to render a real button with type="button"
whenever ColumnSortable(column) is true, and remove the custom keydown handling
since native button behavior will cover Enter and Space.

Comment on lines +442 to +446
<button type="button" class="bit-dtg-btn" disabled="@(CurrentPage <= 1)" @onclick="() => GoToPageAsync(1)">«</button>
<button type="button" class="bit-dtg-btn" disabled="@(CurrentPage <= 1)" @onclick="() => GoToPageAsync(CurrentPage - 1)">‹</button>
<span class="bit-dtg-pager-current">Page @CurrentPage of @TotalPages</span>
<button type="button" class="bit-dtg-btn" disabled="@(CurrentPage >= TotalPages)" @onclick="() => GoToPageAsync(CurrentPage + 1)">›</button>
<button type="button" class="bit-dtg-btn" disabled="@(CurrentPage >= TotalPages)" @onclick="() => GoToPageAsync(TotalPages)">»</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Label the symbol-only pager buttons.

«, , , and » are not meaningful accessible names. Add explicit aria-labels for first/previous/next/last page so the pager is understandable to screen-reader users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor`
around lines 442 - 446, The pager buttons in BitDataGrid’s paging controls use
symbol-only labels, which are not accessible names for screen readers. Update
the button elements in the pager markup so each one has an explicit aria-label
describing its action: first page, previous page, next page, and last page. Keep
the existing click handlers and disabled conditions intact while adding the
accessible labels to the same button symbols.

Comment on lines +418 to +426
var inputsChanged = !ReferenceEquals(Items, _lastItems)
|| PageSize != _lastPageSize
|| (!IsServerMode && !IsInfiniteMode);
if (!_dataInitialized || inputsChanged)
{
_lastItems = Items;
_lastPageSize = PageSize;
_dataInitialized = true;
await RefreshAsync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset tree bootstrap when the root hierarchy changes.

ProcessTreeData() only honors TreeInitiallyExpanded while _treeInitialized is false, but that flag is never cleared when a new Items source arrives. After the first tree load, replacing the hierarchy leaves every new node collapsed even though TreeInitiallyExpanded is still true.

💡 Suggested fix
-        var inputsChanged = !ReferenceEquals(Items, _lastItems)
+        var itemsChanged = !ReferenceEquals(Items, _lastItems);
+        if (itemsChanged && IsTreeMode)
+        {
+            _treeInitialized = false;
+            _expandedTree.Clear();
+            _treeMeta.Clear();
+        }
+
+        var inputsChanged = itemsChanged
             || PageSize != _lastPageSize
             || (!IsServerMode && !IsInfiniteMode);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var inputsChanged = !ReferenceEquals(Items, _lastItems)
|| PageSize != _lastPageSize
|| (!IsServerMode && !IsInfiniteMode);
if (!_dataInitialized || inputsChanged)
{
_lastItems = Items;
_lastPageSize = PageSize;
_dataInitialized = true;
await RefreshAsync();
var itemsChanged = !ReferenceEquals(Items, _lastItems);
if (itemsChanged && IsTreeMode)
{
_treeInitialized = false;
_expandedTree.Clear();
_treeMeta.Clear();
}
var inputsChanged = itemsChanged
|| PageSize != _lastPageSize
|| (!IsServerMode && !IsInfiniteMode);
if (!_dataInitialized || inputsChanged)
{
_lastItems = Items;
_lastPageSize = PageSize;
_dataInitialized = true;
await RefreshAsync();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs`
around lines 418 - 426, Reset the tree initialization state when the data source
changes so `ProcessTreeData()` can reapply `TreeInitiallyExpanded` on a new
hierarchy. In `BitDataGrid`, update the refresh path that checks
`inputsChanged`/`_dataInitialized` to also clear `_treeInitialized` whenever a
new `Items` source is detected (or otherwise when the root tree data changes)
before calling `RefreshAsync()`. This ensures the tree bootstrap logic in
`ProcessTreeData()` runs again for the new items instead of keeping the old
collapsed state.

Comment on lines +18 to +29
if (remaining <= distance) {
pending = true;
// The circuit may disconnect (navigation, refresh) between the disposed check and
// this async call, so swallow the resulting rejection to avoid unhandled console errors.
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync')
.catch(() => { })
.finally(() => {
pending = false;
// Re-check once the load settled: if the viewport is still near the end and
// more data exists, another batch is needed to fill it.
if (!disposed) check();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop auto-retrying when a load doesn't extend the viewport.

Lines 24-28 always call check() again after OnInfiniteScrollNearEndAsync settles. If the callback reaches end-of-data (or returns without adding rows), remaining <= distance stays true and this loops forever, repeatedly reissuing the load callback. Re-run only when the last load actually increased the viewport height, or have the .NET side return a hasMore/addedItems signal.

Proposed fix
         if (remaining <= distance) {
             pending = true;
+            const previousScrollHeight = viewport.scrollHeight;
             // The circuit may disconnect (navigation, refresh) between the disposed check and
             // this async call, so swallow the resulting rejection to avoid unhandled console errors.
             dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync')
                 .catch(() => { })
                 .finally(() => {
                     pending = false;
-                    // Re-check once the load settled: if the viewport is still near the end and
-                    // more data exists, another batch is needed to fill it.
-                    if (!disposed) check();
+                    if (!disposed && viewport.scrollHeight > previousScrollHeight) {
+                        check();
+                    }
                 });
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (remaining <= distance) {
pending = true;
// The circuit may disconnect (navigation, refresh) between the disposed check and
// this async call, so swallow the resulting rejection to avoid unhandled console errors.
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync')
.catch(() => { })
.finally(() => {
pending = false;
// Re-check once the load settled: if the viewport is still near the end and
// more data exists, another batch is needed to fill it.
if (!disposed) check();
});
if (remaining <= distance) {
pending = true;
const previousScrollHeight = viewport.scrollHeight;
// The circuit may disconnect (navigation, refresh) between the disposed check and
// this async call, so swallow the resulting rejection to avoid unhandled console errors.
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync')
.catch(() => { })
.finally(() => {
pending = false;
if (!disposed && viewport.scrollHeight > previousScrollHeight) {
check();
}
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts` around
lines 18 - 29, The infinite-scroll retry logic in BitDataGrid.ts keeps calling
check() after OnInfiniteScrollNearEndAsync settles, which can loop forever when
no new rows are added. Update the pending/finally flow around
dotNetRef.invokeMethodAsync('OnInfiniteScrollNearEndAsync') so check() only runs
again if the last load actually extended the viewport or the .NET callback
reports that more data was added/available. Use the existing disposed/pending
guard in this callback path to prevent re-issuing loads when end-of-data is
reached.

Comment on lines +4 to +8
<div class="@RowClass" role="row" style="@RowStyle" @onclick="OnRowClick"
draggable="@(Grid.RowReorderable ? "true" : null)"
@ondragstart="() => Grid.StartRowDrag(Item)"
@ondragover:preventDefault="Grid.RowReorderable"
@ondrop="() => Grid.DropRowAsync(Item)">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Row-level dragging conflicts with inline editing.

Lines 4-8 make the whole row draggable, and Lines 118-127 later render editable controls inside that same draggable container. In a reorderable + editable grid, drag gestures inside editors can start a row move instead of text selection/input interaction. Limit pointer dragging to the handle, or at least disable row dragging while Editing is true.

Suggested fix
-<div class="`@RowClass`" role="row" style="`@RowStyle`" `@onclick`="OnRowClick"
-     draggable="@(Grid.RowReorderable ? "true" : null)"
+<div class="`@RowClass`" role="row" style="`@RowStyle`" `@onclick`="OnRowClick"
+     draggable="@(Grid.RowReorderable && !Editing ? "true" : null)"
      `@ondragstart`="() => Grid.StartRowDrag(Item)"
      `@ondragover`:preventDefault="Grid.RowReorderable"
      `@ondrop`="() => Grid.DropRowAsync(Item)">

Also applies to: 118-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridRow.razor`
around lines 4 - 8, Row dragging in BitDataGridRow conflicts with inline editing
because the entire row is marked draggable while editable controls are rendered
inside it. Update the BitDataGridRow.razor row container and the editable
content path so drag behavior is either limited to a dedicated handle or
disabled whenever Editing is true. Make sure the drag handlers tied to
Grid.StartRowDrag and Grid.DropRowAsync only activate for reorder interactions
and do not interfere with editor input/selection inside the row.

Comment on lines +198 to +265
private static bool Matches(object? value, BitDataGridFilterDescriptor filter)
{
switch (filter.Operator)
{
case BitDataGridFilterOperator.Unspecified:
// No operator selected: treat the filter as omitted so it doesn't exclude any rows.
return true;
case BitDataGridFilterOperator.IsEmpty:
return value is null || string.IsNullOrEmpty(value.ToString());
case BitDataGridFilterOperator.IsNotEmpty:
return value is not null && !string.IsNullOrEmpty(value.ToString());
}

// Numeric / comparable operators
if (filter.Operator is BitDataGridFilterOperator.GreaterThan or BitDataGridFilterOperator.GreaterThanOrEqual
or BitDataGridFilterOperator.LessThan or BitDataGridFilterOperator.LessThanOrEqual
or BitDataGridFilterOperator.Equals or BitDataGridFilterOperator.NotEquals)
{
// Handle nulls explicitly rather than letting the comparer order nulls-first, which would
// otherwise make a null row value spuriously match LessThan/LessThanOrEqual filters.
if (value is null)
{
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => filter.Value is null,
BitDataGridFilterOperator.NotEquals => filter.Value is not null,
_ => false
};
}

if (filter.Value is null)
{
// Row value is non-null here, so equality against a null filter value is deterministic;
// ordering operators have no meaningful null operand, so they don't match.
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => false,
BitDataGridFilterOperator.NotEquals => true,
_ => false
};
}

var cmp = BitDataGridValueComparer.Instance.Compare(value, CoerceToValueType(value, filter.Value));
return filter.Operator switch
{
BitDataGridFilterOperator.GreaterThan => cmp > 0,
BitDataGridFilterOperator.GreaterThanOrEqual => cmp >= 0,
BitDataGridFilterOperator.LessThan => cmp < 0,
BitDataGridFilterOperator.LessThanOrEqual => cmp <= 0,
BitDataGridFilterOperator.Equals => cmp == 0,
BitDataGridFilterOperator.NotEquals => cmp != 0,
_ => true
};
}

if (filter.Value is null)
return true;

// String operators
var text = value?.ToString() ?? string.Empty;
var term = filter.Value.ToString() ?? string.Empty;
return filter.Operator switch
{
BitDataGridFilterOperator.Contains => text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.DoesNotContain => !text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.StartsWith => text.StartsWith(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.EndsWith => text.EndsWith(term, StringComparison.OrdinalIgnoreCase),
_ => true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat blank filter values as “no filter.”

Matches only treats null as omitted. A cleared editor usually produces "", which makes DoesNotContain reject every row and leaves comparison operators evaluating against an empty string.

Proposed fix
 private static bool Matches(object? value, BitDataGridFilterDescriptor filter)
 {
+    var filterValue = filter.Value is string s && string.IsNullOrWhiteSpace(s)
+        ? null
+        : filter.Value;
+
     switch (filter.Operator)
     {
         case BitDataGridFilterOperator.Unspecified:
             // No operator selected: treat the filter as omitted so it doesn't exclude any rows.
             return true;
@@
-                    BitDataGridFilterOperator.Equals => filter.Value is null,
-                    BitDataGridFilterOperator.NotEquals => filter.Value is not null,
+                    BitDataGridFilterOperator.Equals => filterValue is null,
+                    BitDataGridFilterOperator.NotEquals => filterValue is not null,
                     _ => false
                 };
             }
 
-            if (filter.Value is null)
+            if (filterValue is null)
             {
                 // Row value is non-null here, so equality against a null filter value is deterministic;
                 // ordering operators have no meaningful null operand, so they don't match.
                 return filter.Operator switch
@@
-            var cmp = BitDataGridValueComparer.Instance.Compare(value, CoerceToValueType(value, filter.Value));
+            var cmp = BitDataGridValueComparer.Instance.Compare(value, CoerceToValueType(value, filterValue));
             return filter.Operator switch
             {
@@
-        if (filter.Value is null)
+        if (filterValue is null)
             return true;
 
         // String operators
         var text = value?.ToString() ?? string.Empty;
-        var term = filter.Value.ToString() ?? string.Empty;
+        var term = filterValue.ToString() ?? string.Empty;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static bool Matches(object? value, BitDataGridFilterDescriptor filter)
{
switch (filter.Operator)
{
case BitDataGridFilterOperator.Unspecified:
// No operator selected: treat the filter as omitted so it doesn't exclude any rows.
return true;
case BitDataGridFilterOperator.IsEmpty:
return value is null || string.IsNullOrEmpty(value.ToString());
case BitDataGridFilterOperator.IsNotEmpty:
return value is not null && !string.IsNullOrEmpty(value.ToString());
}
// Numeric / comparable operators
if (filter.Operator is BitDataGridFilterOperator.GreaterThan or BitDataGridFilterOperator.GreaterThanOrEqual
or BitDataGridFilterOperator.LessThan or BitDataGridFilterOperator.LessThanOrEqual
or BitDataGridFilterOperator.Equals or BitDataGridFilterOperator.NotEquals)
{
// Handle nulls explicitly rather than letting the comparer order nulls-first, which would
// otherwise make a null row value spuriously match LessThan/LessThanOrEqual filters.
if (value is null)
{
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => filter.Value is null,
BitDataGridFilterOperator.NotEquals => filter.Value is not null,
_ => false
};
}
if (filter.Value is null)
{
// Row value is non-null here, so equality against a null filter value is deterministic;
// ordering operators have no meaningful null operand, so they don't match.
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => false,
BitDataGridFilterOperator.NotEquals => true,
_ => false
};
}
var cmp = BitDataGridValueComparer.Instance.Compare(value, CoerceToValueType(value, filter.Value));
return filter.Operator switch
{
BitDataGridFilterOperator.GreaterThan => cmp > 0,
BitDataGridFilterOperator.GreaterThanOrEqual => cmp >= 0,
BitDataGridFilterOperator.LessThan => cmp < 0,
BitDataGridFilterOperator.LessThanOrEqual => cmp <= 0,
BitDataGridFilterOperator.Equals => cmp == 0,
BitDataGridFilterOperator.NotEquals => cmp != 0,
_ => true
};
}
if (filter.Value is null)
return true;
// String operators
var text = value?.ToString() ?? string.Empty;
var term = filter.Value.ToString() ?? string.Empty;
return filter.Operator switch
{
BitDataGridFilterOperator.Contains => text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.DoesNotContain => !text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.StartsWith => text.StartsWith(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.EndsWith => text.EndsWith(term, StringComparison.OrdinalIgnoreCase),
_ => true
private static bool Matches(object? value, BitDataGridFilterDescriptor filter)
{
var filterValue = filter.Value is string s && string.IsNullOrWhiteSpace(s)
? null
: filter.Value;
switch (filter.Operator)
{
case BitDataGridFilterOperator.Unspecified:
// No operator selected: treat the filter as omitted so it doesn't exclude any rows.
return true;
case BitDataGridFilterOperator.IsEmpty:
return value is null || string.IsNullOrEmpty(value.ToString());
case BitDataGridFilterOperator.IsNotEmpty:
return value is not null && !string.IsNullOrEmpty(value.ToString());
}
// Numeric / comparable operators
if (filter.Operator is BitDataGridFilterOperator.GreaterThan or BitDataGridFilterOperator.GreaterThanOrEqual
or BitDataGridFilterOperator.LessThan or BitDataGridFilterOperator.LessThanOrEqual
or BitDataGridFilterOperator.Equals or BitDataGridFilterOperator.NotEquals)
{
// Handle nulls explicitly rather than letting the comparer order nulls-first, which would
// otherwise make a null row value spuriously match LessThan/LessThanOrEqual filters.
if (value is null)
{
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => filterValue is null,
BitDataGridFilterOperator.NotEquals => filterValue is not null,
_ => false
};
}
if (filterValue is null)
{
// Row value is non-null here, so equality against a null filter value is deterministic;
// ordering operators have no meaningful null operand, so they don't match.
return filter.Operator switch
{
BitDataGridFilterOperator.Equals => false,
BitDataGridFilterOperator.NotEquals => true,
_ => false
};
}
var cmp = BitDataGridValueComparer.Instance.Compare(value, CoerceToValueType(value, filterValue));
return filter.Operator switch
{
BitDataGridFilterOperator.GreaterThan => cmp > 0,
BitDataGridFilterOperator.GreaterThanOrEqual => cmp >= 0,
BitDataGridFilterOperator.LessThan => cmp < 0,
BitDataGridFilterOperator.LessThanOrEqual => cmp <= 0,
BitDataGridFilterOperator.Equals => cmp == 0,
BitDataGridFilterOperator.NotEquals => cmp != 0,
_ => true
};
}
if (filterValue is null)
return true;
// String operators
var text = value?.ToString() ?? string.Empty;
var term = filterValue.ToString() ?? string.Empty;
return filter.Operator switch
{
BitDataGridFilterOperator.Contains => text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.DoesNotContain => !text.Contains(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.StartsWith => text.StartsWith(term, StringComparison.OrdinalIgnoreCase),
BitDataGridFilterOperator.EndsWith => text.EndsWith(term, StringComparison.OrdinalIgnoreCase),
_ => true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridDataProcessor.cs`
around lines 198 - 265, In BitDataGridDataProcessor.Matches, blank filter values
should be treated the same as omitted filters instead of being evaluated as real
values. Add an early check after the Unspecified/empty handling so that empty or
whitespace-only filter.Value is ignored and returns true, and make sure this
applies before the comparison and string-operator branches so operators like
DoesNotContain and numeric comparisons don’t run against "".

Comment on lines +37 to +45
public void SetValue(TItem item, object? value)
{
if (_setter is null) return;
// Only write when the value can actually be coerced to the property's type. Silently
// substituting the type's default (e.g. 0) for unparseable input would discard the user's
// entry without any feedback, so reject the conversion failure instead.
if (TryConvertValue(value, out var converted))
_setter(item, converted);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the exact code around the accessor and the editing call site(s).
sed -n '1,220p' src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs
printf '\n---\n'
rg -n "Accessor\.SetValue\(|_editItem|EditItem|OnCellEdit|CommitEdit|SaveEdit" src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid -S

Repository: bitfoundation/bitplatform

Length of output: 10452


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the edit/commit flow around the cached edit item and save path.
sed -n '1028,1088p' src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs
printf '\n---\n'
sed -n '960,1035p' src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.razor.cs

Repository: bitfoundation/bitplatform

Length of output: 4719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the grid or accessor constrains TItem to reference types anywhere.
rg -n "`@typeparam` TItem|where TItem|class BitDataGrid|partial class BitDataGrid|struct" src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid -S

Repository: bitfoundation/bitplatform

Length of output: 2555


Value-type rows lose inline edits

SetEditValue(_editItem, value) passes a struct row by value, and BitDataGridPropertyAccessor<TItem>.SetValue(TItem item, ...) only mutates that copy. _editItem never changes, so CommitEditAsync saves the original row. Constrain editing to reference types or update the stored row slot by reference/index.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/Infrastructure/BitDataGridPropertyAccessor.cs`
around lines 37 - 45, The inline edit path in
BitDataGridPropertyAccessor<TItem>.SetValue is mutating only a copied TItem, so
struct rows never persist changes back to the grid state. Fix the edit flow
around SetEditValue and CommitEditAsync so value-type rows are either disallowed
for this editing path or updated by reference/index in the stored row slot
instead of passing the item by value. Use
BitDataGridPropertyAccessor<TItem>.SetValue and the edit-item storage logic to
ensure the committed row reflects the edited values.

Comment on lines +26 to +40
return {
stop: () => {
document.body.removeEventListener('click', bodyClickHandler);
document.body.removeEventListener('mousedown', bodyClickHandler);
document.body.removeEventListener('keydown', keyDownHandler);

// Remove the per-handle drag listeners and clear the bound marker so a later
// init() can rebind the same surviving elements without duplicating handlers.
boundDragHandles.forEach(({ handle, listener }) => {
handle.removeEventListener('mousedown', listener);
handle.removeEventListener('touchstart', listener);
delete handle.__bitQkgResizeBound;
});
boundDragHandles.length = 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Tear down active resize listeners from stop().

stop() only removes the idle bindings. Once a drag has started, the document-level move/up/cancel listeners live inside handleMouseDown, so disposing or re-initializing the grid mid-drag leaves them attached until the gesture ends. That can leak listeners and keep writing widths to a stale th.

💡 Suggested fix
 public static init(tableElement: any) {
     const boundDragHandles: { handle: any, listener: any }[] = [];
+    let stopActiveResize: (() => void) | undefined;
-    QuickGrid.enableColumnResizing(tableElement, boundDragHandles);
+    QuickGrid.enableColumnResizing(tableElement, boundDragHandles, cleanup => stopActiveResize = cleanup);

     return {
         stop: () => {
+            stopActiveResize?.();
+            stopActiveResize = undefined;
+
             document.body.removeEventListener('click', bodyClickHandler);
             document.body.removeEventListener('mousedown', bodyClickHandler);
             document.body.removeEventListener('keydown', keyDownHandler);
             boundDragHandles.forEach(({ handle, listener }) => {
                 handle.removeEventListener('mousedown', listener);
@@
-        private static enableColumnResizing(tableElement: any, boundDragHandles: { handle: any, listener: any }[]) {
+        private static enableColumnResizing(
+            tableElement: any,
+            boundDragHandles: { handle: any, listener: any }[],
+            setActiveCleanup: (cleanup: (() => void) | undefined) => void) {
@@
                 function handleMouseUp() {
                     document.body.removeEventListener('mousemove', handleMouseMove);
                     document.body.removeEventListener('mouseup', handleMouseUp);
                     document.body.removeEventListener('touchmove', handleMouseMove);
                     document.body.removeEventListener('touchend', handleMouseUp);
                     document.body.removeEventListener('touchcancel', handleMouseUp);
+                    setActiveCleanup(undefined);
                 }
+
+                setActiveCleanup(handleMouseUp);

Also applies to: 88-126

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts` around
lines 26 - 40, The stop() cleanup in BitQuickGrid.ts only removes the idle/body
and per-handle bindings, but any document-level listeners added by
handleMouseDown during an active drag can remain attached. Update the stop()
teardown to also unregister the move/up/cancel listeners that handleMouseDown
installs, and clear any active drag state so re-init or disposal cannot keep
mutating a stale th. Use the existing stop() and handleMouseDown symbols to
locate the cleanup and mirror the same listener registration paths when removing
them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The new BitDataGrid component

1 participant